update ios project

update ios project
This commit is contained in:
Zeus
2024-07-24 11:51:39 +08:00
parent 876d355b18
commit 1bf69048f3
275 changed files with 3372 additions and 1015 deletions
@@ -0,0 +1,37 @@
//
// FileManager+Helpers.swift
// PacketTunnel
//
// Created by Jerry Bool on 2022/12/6.
//
import Foundation
extension FileManager {
static var appGroupId = "group.com.vpntunnel.vpnzeus"
private var sharedFolderURL: URL? {
let appGroupId = FileManager.appGroupId
guard let sharedFolderURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
Logger.log("Cannot obtain shared folder URL", to: Logger.vpnLogFile)
return nil
}
return sharedFolderURL
}
var vpnLogFile: URL? {
sharedFolderURL?.appendingPathComponent("log")
}
var leafLogFile: URL? {
sharedFolderURL?.appendingPathComponent("leaf.log")
}
var leafConfFile: URL? {
sharedFolderURL?.appendingPathComponent("leaf.conf")
}
var leafConfTemplateFile: URL? {
Bundle.main.url(forResource: "template", withExtension: "conf")
}
}
@@ -0,0 +1,35 @@
//
// URL+Helpers.swift
// PacketTunnel
//
// Created by Jerry Bool on 2022/12/6.
//
import Foundation
extension URL {
var contents: String? {
guard self.isFileURL else {
return nil
}
do {
return try String(contentsOf: self)
}
catch {
Logger.log(error.localizedDescription, to: Logger.vpnLogFile)
return nil
}
}
@discardableResult
func truncate() -> Self {
if isFileURL {
try? "".write(to: self, atomically: true, encoding: .utf8)
}
return self
}
}
+276
View File
@@ -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
}
}
}
+97
View File
@@ -0,0 +1,97 @@
//
// Logger.swift
// PacketTunnel
//
// Created by Jerry Bool on 2022/12/6.
//
import Foundation
class Logger {
static let ENABLE_LOGGING = true
static var vpnLogFile: URL? = {
FileManager.default.vpnLogFile?.truncate()
}()
static func log(_ message: String, to: URL?) {
guard ENABLE_LOGGING,
let url = to,
let data = message.trimmingCharacters(in: .whitespacesAndNewlines).appending("\n").data(using: .utf8),
let fh = try? FileHandle(forUpdating: url)
else {
return
}
defer {
fh.closeFile()
}
fh.seekToEndOfFile()
fh.write(data)
}
private static var logFsObject: DispatchSourceFileSystemObject? {
didSet {
oldValue?.cancel()
}
}
private static var logText = ""
static func tailFile(_ url: URL?, _ update: ((_ logText: String) -> Void)? = nil) {
// Stop and remove the previous watched content.
// (Will implicitely call #stop through `didSet` hook!)
logFsObject = nil
guard let url = url,
let fh = try? FileHandle(forReadingFrom: url)
else {
return
}
let ui = {
let data = fh.readDataToEndOfFile()
if let content = String(data: data, encoding: .utf8) {
logText.append(content)
update?(logText)
}
}
logText = ""
ui()
logFsObject = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fh.fileDescriptor,
eventMask: [.extend, .delete, .link],
queue: .main)
logFsObject?.setEventHandler {
guard let data = logFsObject?.data else {
return
}
if data.contains(.delete) || data.contains(.link) {
DispatchQueue.main.async {
tailFile(url, update)
}
}
if data.contains(.extend) {
ui()
}
}
logFsObject?.setCancelHandler {
try? fh.close()
logText = ""
}
logFsObject?.resume()
}
}
@@ -0,0 +1,67 @@
import NetworkExtension
//import LeafFFI
import leaf
class PacketTunnelProvider: NEPacketTunnelProvider {
private lazy var adapter: LeafAdapater = {
LeafAdapater.setPacketTunnelProvider(with: self)
return LeafAdapater.shared()
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let ipv4 = NEIPv4Settings(addresses: ["198.18.20.20"], subnetMasks: ["255.255.255.0"])
ipv4.includedRoutes = [NEIPv4Route.default()]
let ipv6 = NEIPv6Settings(addresses: ["FD00::9999:9999"], networkPrefixLengths: [7])
ipv6.includedRoutes = [NEIPv6Route.default()]
let dns = NEDNSSettings(servers: ["1.1.1.1"])
// https://developer.apple.com/forums/thread/116033
// Mention special Tor domains here, so the OS doesn't drop onion domain
// resolve requests immediately.
dns.matchDomains = ["", "onion", "exit"]
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "198.18.20.200")
settings.ipv4Settings = ipv4
settings.ipv6Settings = ipv6
settings.dnsSettings = dns
settings.proxySettings = nil
settings.mtu = 1500
self.adapter.start(completionHandler: completionHandler)
setTunnelNetworkSettings(settings) { error in
if let error = error {
return completionHandler(error)
}
completionHandler(nil)
}
}
// override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// self.adapter.stop { error in
// if let error = error {
// Logger.log(error.localizedDescription, to: Logger.vpnLogFile)
// }
//
// completionHandler()
// }
// }
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: @escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
}
@@ -0,0 +1,26 @@
//
// TunnelConfiguration.swift
// Runner
//
// Created by Jerry Bool on 2022/12/7.
//
import Foundation
public final class TunnelConfiguration {
public var name: String?
public var content: String?
public init(name: String?, content: String?) {
self.name = name
self.content = content
}
}
extension TunnelConfiguration: Equatable {
public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool {
return lhs.name == rhs.name &&
lhs.name == rhs.name
}
}
+94
View File
@@ -0,0 +1,94 @@
import Foundation
import NetworkExtension
//import LeafFFI
import leaf
extension NEVPNStatus: CustomStringConvertible {
public var description: String {
switch self {
case .disconnected: return "Disconnected"
case .invalid: return "Invalid"
case .connected: return "Connected"
case .connecting: return "Connecting"
case .disconnecting: return "Disconnecting"
case .reasserting: return "Reasserting"
default: return "Unknowed"
}
}
}
public class VPNManager {
public var manager = NETunnelProviderManager.shared()
private static var sharedVPNManager: VPNManager = {
return VPNManager()
}()
public class func shared() -> VPNManager {
return sharedVPNManager
}
public init() {}
public func getStatus() -> NEVPNStatus {
return manager.connection.status
}
public func getConnectedDate() -> Date? {
return manager.connection.connectedDate
}
public func loadVPNPreference(completion: @escaping (Error?) -> Void) {
NETunnelProviderManager.loadAllFromPreferences() { managers, error in
guard let managers = managers, error == nil else {
completion(error)
return
}
if managers.count == 0 {
let newManager = NETunnelProviderManager()
newManager.protocolConfiguration = NETunnelProviderProtocol()
newManager.localizedDescription = "UUVPN"
newManager.protocolConfiguration?.serverAddress = "iLeaf"
newManager.saveToPreferences { error in
guard error == nil else {
completion(error)
return
}
newManager.loadFromPreferences { error in
self.manager = newManager
completion(nil)
}
}
} else {
self.manager = managers[0]
completion(nil)
}
}
}
public func enableVPNManager(completion: @escaping (Error?) -> Void) {
manager.isEnabled = true
manager.saveToPreferences { error in
guard error == nil else {
completion(error)
return
}
self.manager.loadFromPreferences { error in
completion(error)
}
}
}
public func toggleVPNConnection(completion: @escaping (Error?) -> Void) {
if self.manager.connection.status == .disconnected || self.manager.connection.status == .invalid {
do {
try self.manager.connection.startVPNTunnel()
} catch {
completion(error)
}
} else {
self.manager.connection.stopVPNTunnel()
}
}
}
+20
View File
@@ -0,0 +1,20 @@
[General]
loglevel = info
dns-server = 223.5.5.5, 114.114.114.114
routing-domain-resolve = true
# Local HTTP CONNECT proxy
interface = 127.0.0.1
port = 1087
# Local SOCKS5 proxy with UDP Associate support
socks-interface = 127.0.0.1
socks-port = 1086
[Proxy]
Direct = direct
Reject = reject
[Rule]
EXTERNAL, site:cn, Direct
FINAL, Direct