Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/Runtime/RuntimeSocketOwnershipProbe.swift
Qiu Yuzhou 856d6b4272 refactor(domain): 按主题重组 Domain/ 源码目录
Domain/ 根目录原有 62 个平铺源文件,无法从目录读出主题划分。

按「目录即主题」重组为 11 个目录(Rules/Custom 为嵌套子目录):Catalog /
Subscription / Rules / Runtime / Activation / Settings / SystemProxy /
Plugin / Credentials / LegacyImport / Diagnostics。纯移动:类型名、模块名
与 import 均未改动,Domain/ 根下不再有散落文件。目录口径取自
GLOSSARY.md 的章节划分(配置与订阅、代理规则、代理控制与网络、设置、
凭据与诊断),而非新造分类。

配套改动:

- project.yml:wrapper 与 helper 两个 target 按文件引用的 15 条显式路径
  随目录改名,并补注释固定这一约定——这 15 条指向不存在的文件时,
  xcodegen generate 以 "missing source directory" 直接失败(实测,不静默)。
- SkeletonTests 新增 DomainLayoutTests:断言 Domain/ 根下无平铺 .swift、
  树下只有 .swift。后者防的是与 App/ 同源的坑——Domain/ 整目录是 app 与
  Core 的 source path,任何非源码文件都会被 XcodeGen 收进 Resources 阶段
  并封进签名后的 bundle。
- README 工程结构说明同步;docs/design 与 docs/research 属历史文档不改。

验证:xcodegen generate 后 app 164 / Core 163 源文件数与整理前一致;Agent
恰好 9 个、Helper 恰好 6 个 Domain 文件(无缺失无多出);app 与 Core 各覆盖
全部 62 个。clean build 0 error;task test 1024 用例全通过(整理前 1022,
+2 为新增守卫);task format 未改写任何被移动文件(证明纯移动);task lint
与整理前同为 21 条内容型 warning;packaging-gate.sh ALL CHECKS PASSED。

布局守卫已做变异验证:临时投放松散 .swift 与非 .swift 文件时两个用例均
失败并点名违规文件,清理后复验为绿。
2026-10-05 20:13:35 +08:00

55 lines
2.0 KiB
Swift

import Darwin
/// Read the kernel's descriptor table to distinguish a candidate listener from
/// another process that happens to answer on the same shared port.
enum RuntimeSocketOwnershipProbe {
static func process(_ processID: Int32, ownsTCPListenerOn port: Int) -> Bool {
guard processID > 0, (1...65535).contains(port) else { return false }
let requiredBytes = proc_pidinfo(processID, PROC_PIDLISTFDS, 0, nil, 0)
let descriptorSize = MemoryLayout<proc_fdinfo>.size
guard requiredBytes >= descriptorSize else { return false }
var descriptors = [proc_fdinfo](
repeating: proc_fdinfo(), count: Int(requiredBytes) / descriptorSize)
let returnedBytes = descriptors.withUnsafeMutableBufferPointer { buffer in
guard let baseAddress = buffer.baseAddress else { return Int32(0) }
return proc_pidinfo(
processID,
PROC_PIDLISTFDS,
0,
UnsafeMutableRawPointer(baseAddress),
Int32(buffer.count * descriptorSize))
}
guard returnedBytes >= descriptorSize else { return false }
let descriptorCount = min(Int(returnedBytes) / descriptorSize, descriptors.count)
for descriptor in descriptors.prefix(descriptorCount)
where descriptor.proc_fdtype == UInt32(PROX_FDTYPE_SOCKET) {
var socketInfo = socket_fdinfo()
let socketInfoSize = MemoryLayout<socket_fdinfo>.size
let socketInfoBytes = proc_pidfdinfo(
processID,
descriptor.proc_fd,
PROC_PIDFDSOCKETINFO,
&socketInfo,
Int32(socketInfoSize))
guard socketInfoBytes == socketInfoSize else { continue }
let socket = socketInfo.psi
guard socket.soi_kind == SOCKINFO_TCP,
socket.soi_type == SOCK_STREAM,
socket.soi_protocol == IPPROTO_TCP,
socket.soi_proto.pri_tcp.tcpsi_state == TSI_S_LISTEN
else {
continue
}
let rawPort = UInt16(truncatingIfNeeded: socket.soi_proto.pri_tcp.tcpsi_ini.insi_lport)
if Int(UInt16(bigEndian: rawPort)) == port { return true }
}
return false
}
}