mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-02-15 16:25:26 +00:00
Work on making DNUpdate use an async timer implementation
This commit is contained in:
parent
e4940d3e3a
commit
8ee9979b16
4 changed files with 93 additions and 72 deletions
|
@ -1,7 +1,7 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
import os.log
|
import NetworkExtension
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
import os.log
|
||||||
|
|
||||||
enum VPNStartError: Error {
|
enum VPNStartError: Error {
|
||||||
case noManagers
|
case noManagers
|
||||||
|
@ -23,7 +23,6 @@ extension AppMessageError: LocalizedError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME: marked as unchecked Sendable to allow sending `self.pathUpdate`, but we should refactor and re-enable linting.
|
// FIXME: marked as unchecked Sendable to allow sending `self.pathUpdate`, but we should refactor and re-enable linting.
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
private var networkMonitor: NWPathMonitor?
|
private var networkMonitor: NWPathMonitor?
|
||||||
|
@ -35,7 +34,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
private var didSleep = false
|
private var didSleep = false
|
||||||
private var cachedRouteDescription: String?
|
private var cachedRouteDescription: String?
|
||||||
|
|
||||||
override func startTunnel(options: [String : NSObject]? = nil) async throws {
|
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
||||||
// There is currently no way to get initialization errors back to the UI via completionHandler here
|
// There is currently no way to get initialization errors back to the UI via completionHandler here
|
||||||
// `expectStart` is sent only via the UI which means we should wait for the real start command which has another completion handler the UI can intercept
|
// `expectStart` is sent only via the UI which means we should wait for the real start command which has another completion handler the UI can intercept
|
||||||
if options?["expectStart"] != nil {
|
if options?["expectStart"] != nil {
|
||||||
|
@ -56,7 +55,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
do {
|
do {
|
||||||
// Cannot use NETunnelProviderManager.loadAllFromPreferences() in earlier versions of iOS
|
// Cannot use NETunnelProviderManager.loadAllFromPreferences() in earlier versions of iOS
|
||||||
// TODO: Remove else once we drop support for iOS 16
|
// TODO: Remove else once we drop support for iOS 16
|
||||||
if ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0)) {
|
if ProcessInfo().isOperatingSystemAtLeast(
|
||||||
|
OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0))
|
||||||
|
{
|
||||||
manager = try await self.findManager()
|
manager = try await self.findManager()
|
||||||
guard let foundManager = manager else {
|
guard let foundManager = manager else {
|
||||||
throw VPNStartError.couldNotFindManager
|
throw VPNStartError.couldNotFindManager
|
||||||
|
@ -88,7 +89,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
// Make sure our ip is routed to the tun device
|
// Make sure our ip is routed to the tun device
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
||||||
|
@ -97,7 +98,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
// Add our unsafe routes
|
// Add our unsafe routes
|
||||||
try _site.unsafeRoutes.forEach { unsafeRoute in
|
try _site.unsafeRoutes.forEach { unsafeRoute in
|
||||||
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
||||||
|
@ -108,7 +109,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
|
|
||||||
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
||||||
var nebulaErr: NSError?
|
var nebulaErr: NSError?
|
||||||
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr)
|
self.nebula = MobileNebulaNewNebula(
|
||||||
|
String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr)
|
||||||
self.startNetworkMonitor()
|
self.startNetworkMonitor()
|
||||||
|
|
||||||
if nebulaErr != nil {
|
if nebulaErr != nil {
|
||||||
|
@ -130,11 +132,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Sleep/wake get called aggressively and do nothing to help us here, we should locate why that is and make these work appropriately
|
//TODO: Sleep/wake get called aggressively and do nothing to help us here, we should locate why that is and make these work appropriately
|
||||||
// override func sleep(completionHandler: @escaping () -> Void) {
|
// override func sleep(completionHandler: @escaping () -> Void) {
|
||||||
// nebula!.sleep()
|
// nebula!.sleep()
|
||||||
// completionHandler()
|
// completionHandler()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private func findManager() async throws -> NETunnelProviderManager {
|
private func findManager() async throws -> NETunnelProviderManager {
|
||||||
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol
|
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol
|
||||||
|
@ -151,7 +153,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
throw VPNStartError.noProviderConfig
|
throw VPNStartError.noProviderConfig
|
||||||
}
|
}
|
||||||
let id = mgrProviderConfig["id"] as? String
|
let id = mgrProviderConfig["id"] as? String
|
||||||
if (id == targetID) {
|
if id == targetID {
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +192,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
|
|
||||||
static private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
|
static private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
|
||||||
var str: [String] = []
|
var str: [String] = []
|
||||||
endpoints.forEach{ endpoint in
|
endpoints.forEach { endpoint in
|
||||||
switch endpoint {
|
switch endpoint {
|
||||||
case let .hostPort(.ipv6(host), port):
|
case let .hostPort(.ipv6(host), port):
|
||||||
str.append("[\(host)]:\(port)")
|
str.append("[\(host)]:\(port)")
|
||||||
|
@ -223,7 +225,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
defer {
|
defer {
|
||||||
self.cancelTunnelWithError(error)
|
self.cancelTunnelWithError(error)
|
||||||
}
|
}
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error.localizedDescription)))
|
return try? JSONEncoder().encode(
|
||||||
|
IPCResponse.init(type: .error, message: JSON(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,8 +248,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
error = AppMessageError.unknownIPCType(command: call.command)
|
error = AppMessageError.unknownIPCType(command: call.command)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
|
return try? JSONEncoder().encode(
|
||||||
|
IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
|
||||||
} else {
|
} else {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
|
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
|
||||||
}
|
}
|
||||||
|
@ -307,4 +311,3 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,26 @@ class SiteList {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func loadAllAsync() async -> Result<[String: Site], any Error> {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
SiteList.loadAllFromFS { sites, err in
|
||||||
|
if err != nil {
|
||||||
|
continuation.resume(returning: Result.failure(err!))
|
||||||
|
}
|
||||||
|
continuation.resume(returning: Result.success(sites!))
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
SiteList.loadAllFromNETPM { sites, err in
|
||||||
|
if err != nil {
|
||||||
|
continuation.resume(returning: Result.failure(err!))
|
||||||
|
}
|
||||||
|
continuation.resume(returning: Result.success(sites!))
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func loadAllFromFS(completion: @escaping ([String: Site]?, (any Error)?) -> Void) {
|
private static func loadAllFromFS(completion: @escaping ([String: Site]?, (any Error)?) -> Void) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
var siteDirs: [URL]
|
var siteDirs: [URL]
|
||||||
|
|
|
@ -26,15 +26,13 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
for await site in dnUpdater.siteUpdates {
|
for await site in await dnUpdater.siteUpdates {
|
||||||
self.sites?.updateSite(site: site)
|
self.sites?.updateSite(site: site)
|
||||||
// Send the refresh sites command on the main thread
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
// Signal to the main screen to reload
|
// Signal to the main screen to reload
|
||||||
self.ui?.invokeMethod("refreshSites", arguments: nil)
|
self.ui?.invokeMethod("refreshSites", arguments: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
guard let controller = window?.rootViewController as? FlutterViewController else {
|
guard let controller = window?.rootViewController as? FlutterViewController else {
|
||||||
fatalError("rootViewController is not type FlutterViewController")
|
fatalError("rootViewController is not type FlutterViewController")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
class DNUpdater {
|
actor DNUpdater {
|
||||||
private let apiClient = APIClient()
|
private let apiClient = APIClient()
|
||||||
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
|
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
|
||||||
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
|
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
|
||||||
|
@ -18,7 +18,7 @@ class DNUpdater {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateSite(site: site, onUpdate: onUpdate)
|
await self.updateSite(site: site, onUpdate: onUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,20 @@ class DNUpdater {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Site updates provides an async/await alternative to `.updateAllLoop` that doesn't require a sendable closure.
|
||||||
|
// https://developer.apple.com/documentation/swift/asyncstream
|
||||||
|
var siteUpdates: AsyncStream<Site> {
|
||||||
|
AsyncStream { continuation in
|
||||||
|
timer.eventHandler = {
|
||||||
|
self.updateAll(onUpdate: { site in
|
||||||
|
continuation.yield(site)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
timer.resume()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateAllLoop(onUpdate: @Sendable @escaping (Site) -> Void) {
|
func updateAllLoop(onUpdate: @Sendable @escaping (Site) -> Void) {
|
||||||
timer.eventHandler = {
|
timer.eventHandler = {
|
||||||
self.updateAll(onUpdate: onUpdate)
|
self.updateAll(onUpdate: onUpdate)
|
||||||
|
@ -95,20 +109,6 @@ class DNUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DNUpdater {
|
|
||||||
// Site updates provides an async/await alternative to `.updateAllLoop` that doesn't require a sendable closure.
|
|
||||||
// https://developer.apple.com/documentation/swift/asyncstream
|
|
||||||
var siteUpdates: AsyncStream<Site> {
|
|
||||||
AsyncStream { continuation in
|
|
||||||
self.updateAllLoop(onUpdate: { site in
|
|
||||||
continuation.yield(site)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
|
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
|
||||||
class RepeatingTimer {
|
class RepeatingTimer {
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue