Add SwiftUI + Network Extension (LibXray/hev), parse Clash Meta YAML from subscription URLs into vless/trojan/hy2 share links, keep the selected server on connect, and ship Navis-3.8.2.13.ipa with versioned changelog. Co-authored-by: Cursor <cursoragent@cursor.com>
43 lines
1.3 KiB
Swift
43 lines
1.3 KiB
Swift
import Foundation
|
|
import Darwin
|
|
import NetworkExtension
|
|
|
|
enum TunFileDescriptor {
|
|
/// Locate the utun socket used by NEPacketTunnelProvider (unsupported but widely used).
|
|
static func find() -> Int32? {
|
|
if let fd = packetFlowKVO() {
|
|
return fd
|
|
}
|
|
return findUtunByGetsockopt()
|
|
}
|
|
|
|
private static func packetFlowKVO() -> Int32? {
|
|
// Caller may set via associated object; left for PacketTunnelProvider to try first.
|
|
nil
|
|
}
|
|
|
|
private static func findUtunByGetsockopt() -> Int32? {
|
|
var buf = [CChar](repeating: 0, count: Int(IFNAMSIZ))
|
|
let prefix = Array("utun".utf8CString.dropLast())
|
|
for fd: Int32 in 0...1024 {
|
|
var len = socklen_t(buf.count)
|
|
// SOL_LOCAL=0 / SYSPROTO_CONTROL path varies; use getsockopt(fd, 2, 2) like WireGuard/others.
|
|
if getsockopt(fd, 2, 2, &buf, &len) == 0 {
|
|
if buf.starts(with: prefix) {
|
|
return fd
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
extension NEPacketTunnelProvider {
|
|
func resolveTunnelFileDescriptor() -> Int32? {
|
|
if let fd = packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32, fd >= 0 {
|
|
return fd
|
|
}
|
|
return TunFileDescriptor.find()
|
|
}
|
|
}
|