update ios project
update ios project
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// LeafAdapter.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Jerry Bool on 2022/12/7.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
//import LeafFFI
|
||||
import leaf
|
||||
|
||||
public enum LeafAdapterError: Error {
|
||||
/// Failure to locate tunnel file descriptor.
|
||||
case cannotLocateTunnelFileDescriptor
|
||||
|
||||
/// Failure to perform an operation in such state.
|
||||
case invalidState
|
||||
|
||||
/// Failure to set network settings.
|
||||
case setNetworkSettings(Error)
|
||||
|
||||
/// Failure to set tunnel configuration
|
||||
case setTunnelConfiguration(Int32)
|
||||
|
||||
/// Failure to start Leaf FFI.
|
||||
case startLeafFFI
|
||||
}
|
||||
|
||||
/// Enum representing internal state of the `LeafAdapter`
|
||||
private enum State {
|
||||
/// The tunnel is stopped
|
||||
case stopped
|
||||
|
||||
/// The tunnel is up and running
|
||||
case started
|
||||
|
||||
/// The tunnel is temporarily shutdown due to device going offline
|
||||
case temporaryShutdown
|
||||
}
|
||||
|
||||
private extension Network.NWPath.Status {
|
||||
/// Returns `true` if the path is potentially satisfiable.
|
||||
var isSatisfiable: Bool {
|
||||
switch self {
|
||||
case .requiresConnection, .satisfied:
|
||||
return true
|
||||
case .unsatisfied:
|
||||
return false
|
||||
@unknown default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LeafAdapater {
|
||||
private static var sharedLeafAdapater: LeafAdapater = {
|
||||
return LeafAdapater()
|
||||
}()
|
||||
|
||||
public class func shared() -> LeafAdapater {
|
||||
return sharedLeafAdapater
|
||||
}
|
||||
|
||||
/// Leaf instance id
|
||||
public static let leafId: UInt16 = 666
|
||||
|
||||
/// Network routes monitor.
|
||||
private var networkMonitor: NWPathMonitor?
|
||||
|
||||
/// Packet tunnel provider.
|
||||
private static weak var packetTunnelProvider: NEPacketTunnelProvider?
|
||||
|
||||
/// Private queue used to synchronize access to `LeafAdapter` members.
|
||||
private let workQueue = DispatchQueue(label: "LeafAdapterWorkQueue")
|
||||
|
||||
/// Adapter state.
|
||||
private var state: State = .stopped
|
||||
|
||||
var tunnelFd: Int32? {
|
||||
var buf = [CChar](repeating: 0, count: Int(IFNAMSIZ))
|
||||
|
||||
for fd: Int32 in 0 ... 1024 {
|
||||
var len = socklen_t(buf.count)
|
||||
|
||||
if getsockopt(fd, 2 /* IGMP */, 2, &buf, &len) == 0 && String(cString: buf).hasPrefix("utun") {
|
||||
return fd
|
||||
}
|
||||
}
|
||||
|
||||
return LeafAdapater.packetTunnelProvider?.packetFlow.value(forKey: "socket.fileDescriptor") as? Int32
|
||||
}
|
||||
|
||||
/// Set PacketTunnelProvider instance
|
||||
/// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored
|
||||
/// as a weak
|
||||
public static func setPacketTunnelProvider(with packetTunnelProvider: NEPacketTunnelProvider) {
|
||||
LeafAdapater.packetTunnelProvider = packetTunnelProvider
|
||||
}
|
||||
|
||||
/// Designated initializer.
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Cancel network monitor
|
||||
networkMonitor?.cancel()
|
||||
|
||||
// Shutdown the tunnel
|
||||
if case .started = self.state {
|
||||
leaf_shutdown(LeafAdapater.leafId)
|
||||
}
|
||||
}
|
||||
|
||||
public func setRuntimeConfiguration(conf: String?, completionHandler: @escaping (LeafAdapterError?) -> Void) {
|
||||
guard let conf = conf else {
|
||||
completionHandler(.startLeafFFI)
|
||||
return
|
||||
}
|
||||
|
||||
let file = FileManager.default.leafConfFile
|
||||
|
||||
try! conf.write(to: file!, atomically: true, encoding: .utf8)
|
||||
|
||||
setenv("LOG_NO_COLOR", "true", 1)
|
||||
|
||||
let result = leaf_test_config(file?.path)
|
||||
guard result == 0 else {
|
||||
completionHandler(.setTunnelConfiguration(result))
|
||||
return
|
||||
}
|
||||
|
||||
completionHandler(nil)
|
||||
}
|
||||
|
||||
/// Returns a runtime configuration.
|
||||
/// - Parameter completionHandler: completion handler.
|
||||
public func getRuntimeConfiguration(completionHandler: @escaping (String?) -> Void) {
|
||||
workQueue.async {
|
||||
let fm = FileManager.default
|
||||
|
||||
if let conf = fm.leafConfFile?.contents {
|
||||
completionHandler(conf)
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the tunnel tunnel.
|
||||
/// - Parameters:
|
||||
/// - tunnelConfiguration: tunnel configuration.
|
||||
/// - completionHandler: completion handler.
|
||||
public func start(completionHandler: @escaping (LeafAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
guard case .stopped = self.state else {
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
let networkMonitor = NWPathMonitor()
|
||||
networkMonitor.pathUpdateHandler = { [weak self] path in
|
||||
self?.didReceivePathUpdate(path: path)
|
||||
}
|
||||
networkMonitor.start(queue: self.workQueue)
|
||||
|
||||
let tunFd = self.tunnelFd != nil ? String(self.tunnelFd!) : nil
|
||||
|
||||
// Reset log file.
|
||||
FileManager.default.leafLogFile?.truncate()
|
||||
|
||||
let fm = FileManager.default
|
||||
let file = fm.leafConfFile
|
||||
var conf = file?.contents ?? ""
|
||||
|
||||
conf = conf
|
||||
.replacingOccurrences(of: "{{leafLogFile}}", with: fm.leafLogFile?.path ?? "")
|
||||
.replacingOccurrences(of: "{{tunFd}}", with: tunFd ?? "")
|
||||
|
||||
try! conf.write(to: file!, atomically: true, encoding: .utf8)
|
||||
|
||||
setenv("LOG_NO_COLOR", "true", 1)
|
||||
|
||||
leaf_run(LeafAdapater.leafId, file?.path)
|
||||
|
||||
self.state = .started
|
||||
self.networkMonitor = networkMonitor
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the tunnel.
|
||||
/// - Parameter completionHandler: completion handler.
|
||||
public func stop(completionHandler: @escaping (LeafAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
switch self.state {
|
||||
case .started:
|
||||
leaf_shutdown(LeafAdapater.leafId)
|
||||
|
||||
case .temporaryShutdown:
|
||||
break
|
||||
|
||||
case .stopped:
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
self.networkMonitor?.cancel()
|
||||
self.networkMonitor = nil
|
||||
|
||||
self.state = .stopped
|
||||
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Update runtime configuration.
|
||||
/// - Parameters:
|
||||
/// - tunnelConfiguration: tunnel configuration.
|
||||
/// - completionHandler: completion handler.
|
||||
public func update(conf: String?, completionHandler: @escaping (LeafAdapterError?) -> Void) {
|
||||
workQueue.async {
|
||||
if case .stopped = self.state {
|
||||
completionHandler(.invalidState)
|
||||
return
|
||||
}
|
||||
|
||||
// Tell the system that the tunnel is going to reconnect using new configuration.
|
||||
// This will broadcast the `NEVPNStatusDidChange` notification to the GUI process.
|
||||
LeafAdapater.packetTunnelProvider?.reasserting = true
|
||||
defer {
|
||||
LeafAdapater.packetTunnelProvider?.reasserting = false
|
||||
}
|
||||
|
||||
switch self.state {
|
||||
case .started:
|
||||
self.setRuntimeConfiguration(conf: conf, completionHandler: completionHandler)
|
||||
|
||||
leaf_reload(LeafAdapater.leafId)
|
||||
|
||||
self.state = .started
|
||||
|
||||
case .temporaryShutdown:
|
||||
self.state = .temporaryShutdown
|
||||
|
||||
case .stopped:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method used by network path monitor.
|
||||
/// - Parameter path: new network path
|
||||
private func didReceivePathUpdate(path: Network.NWPath) {
|
||||
switch self.state {
|
||||
case .started:
|
||||
if path.status.isSatisfiable {
|
||||
|
||||
} else {
|
||||
self.state = .temporaryShutdown
|
||||
leaf_shutdown(LeafAdapater.leafId)
|
||||
}
|
||||
|
||||
case .temporaryShutdown:
|
||||
guard path.status.isSatisfiable else { return }
|
||||
self.state = .started
|
||||
|
||||
case .stopped:
|
||||
// no-op
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user