You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MacOS下Per-App VPN从NEPacketTunnelFlow获取PID遇sourceAppAuditToken为空

问题分析

NEPacketTunnelProvider的设计定位是处理隧道层面的数据包转发,而非应用层流量过滤,因此系统不会自动将sourceAppAuditToken附加到NEPacketTunnelFlow的数据包元数据中——该字段仅为NEFilterProvider(内容过滤扩展)设计,两者的权限模型和数据传递逻辑完全不同,这就是你始终拿到nil的核心原因。

替代解决方案:通过IP+端口映射获取PID

在MacOS上,可通过解析系统网络连接表,将数据包的源IP、端口与进程ID做关联,具体实现如下:

1. 提取数据包的源IP和端口

从NEPacket的networkProtocol字段中获取关键信息:

self.packetFlow.readPacketObjects { packets in
    for packet in packets {                
        guard let networkProtocol = packet.networkProtocol else { continue }
        let sourceAddr = networkProtocol.sourceAddress
        let sourcePort = networkProtocol.sourcePort
        
        // 调用方法通过IP+端口查找PID
        if let pid = self.pidForSocketAddress(address: sourceAddr, port: sourcePort) {
            log.debug("PID: \(pid)")
        } else {
            log.debug("无法匹配到对应进程")
        }
    }
}

2. 解析系统网络连接表(sysctl实现)

通过sysctl读取系统TCP/UDP连接表,匹配目标IP和端口对应的PID:

import Foundation
import System

// 适配MacOS的TCP连接结构体(需根据系统版本调整字段)
struct inpcb {
    let inp_len: UInt16
    let inp_family: UInt8
    let inp_type: UInt8
    let inp_protocol: UInt8
    let inp_flags: UInt16
    let inp_lport: UInt16
    let inp_laddr: in_addr
    let inp_faddr: in_addr
    let inp_pid: pid_t
    // IPv6相关字段需额外添加,此处仅示例IPv4逻辑
}

func pidForSocketAddress(address: sockaddr_storage, port: UInt16) -> pid_t? {
    var mib: [Int32] = [CTL_NET, PF_INET, SOCK_STREAM, NET_INET_DIAG, NET_INET_DIAG_PCB]
    var len: size_t = 0
    
    // 获取缓冲区大小
    guard sysctl(&mib, UInt32(mib.count), nil, &len, nil, 0) >= 0 else { return nil }
    
    var buffer = [UInt8](repeating: 0, count: len)
    guard sysctl(&mib, UInt32(mib.count), &buffer, &len, nil, 0) >= 0 else { return nil }
    
    // 遍历所有连接条目
    var offset = 0
    while offset < len {
        let pcb = buffer.withUnsafeBytes { ptr in
            ptr.baseAddress!.advanced(by: offset).assumingMemoryBound(to: inpcb.self)
        }.pointee
        
        // 匹配端口和IP地址
        if pcb.inp_lport == port, address.ss_family == AF_INET {
            let srcAddr = address.withUnsafeBytes { ptr in
                ptr.baseAddress!.assumingMemoryBound(to: sockaddr_in.self).pointee.sin_addr.s_addr
            }
            if pcb.inp_laddr.s_addr == srcAddr {
                return pcb.inp_pid
            }
        }
        
        offset += Int(pcb.inp_len)
    }
    
    return nil
}

3. 沙箱权限配置

若你的Packet Tunnel Extension启用了沙箱,需在entitlements文件中添加以下权限:

<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.sysctl</key>
<array>
    <string>net.inet.tcp.pcblist</string>
    <string>net.inet.udp.pcblist</string>
</array>
额外注意事项
  • 该方法仅支持TCP/UDP协议,ICMP等无端口协议无法匹配PID;
  • 若多个进程复用同一IP:端口(如启用SO_REUSEPORT),需额外逻辑处理多PID场景;
  • MacOS 12+对sysctl访问限制更严格,若遇到权限问题,可改用libproc库的proc_listallpids+proc_pidinfo遍历进程并匹配网络连接。

内容的提问来源于stack exchange,提问作者emanuele-em

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 13:35:27