Refactor DNUpdater to an actor and try to fix warnings

This commit is contained in:
Caleb Jasik 2025-02-19 15:51:47 -06:00
parent f59ab7c09d
commit be56774197
No known key found for this signature in database
4 changed files with 44 additions and 45 deletions

View file

@ -23,7 +23,7 @@ extension AppMessageError: LocalizedError {
}
}
class PacketTunnelProvider: NEPacketTunnelProvider {
final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
private var networkMonitor: NWPathMonitor?
private var site: Site?
@ -92,8 +92,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
throw err!
}
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(
addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]
)
addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
var routes: [NEIPv4Route] = [
NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)
]
@ -113,8 +112,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
try await setTunnelNetworkSettings(tunnelNetworkSettings)
var nebulaErr: NSError?
nebula = MobileNebulaNewNebula(
String(data: config, encoding: .utf8), key, site!.logFile, tunFD, &nebulaErr
)
String(data: config, encoding: .utf8), key, site!.logFile, tunFD, &nebulaErr)
startNetworkMonitor()
if nebulaErr != nil {
@ -123,15 +121,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
nebula!.start()
dnUpdater.updateSingleLoop(site: site!, onUpdate: handleDNUpdate)
await dnUpdater.updateSingleLoop(site: site!, onUpdate: handleDNUpdate)
}
private func handleDNUpdate(newSite: Site) {
do {
site = newSite
try nebula?.reload(
String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey()
)
try nebula?.reload(String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey())
} catch {
log.error(
@ -180,9 +176,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
networkMonitor = nil
}
override func stopTunnel(
with _: NEProviderStopReason, completionHandler: @escaping () -> Void
) {
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
nebula?.stop()
stopNetworkMonitor()
completionHandler()
@ -259,9 +253,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
if error != nil {
return try? JSONEncoder().encode(
IPCResponse(
type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")
))
IPCResponse(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
} else {
return try? JSONEncoder().encode(IPCResponse(type: .success, message: data))
}
@ -276,14 +268,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func getHostInfo(args: JSON) -> (JSON?, (any Error)?) {
var err: NSError?
let res = nebula!.getHostInfo(
byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err
)
return (JSON(res), err)
}
private func setRemoteForTunnel(args: JSON) -> (JSON?, (any Error)?) {
var err: NSError?
let res = nebula!.setRemoteForTunnel(
args["vpnIp"].string, addr: args["addr"].string, error: &err)
args["vpnIp"].string, addr: args["addr"].string, error: &err
)
return (JSON(res), err)
}

View file

@ -150,7 +150,7 @@ let statusString: [NEVPNStatus: String] = [
]
// Represents a site that was pulled out of the system configuration
class Site: Codable {
final class Site: Codable, @unchecked Sendable {
// Stored in manager
var name: String
var id: String
@ -278,7 +278,10 @@ class Site: Codable {
}
}
if hasErrors && !managed {
if hasErrors, !managed {
errors.append("There are issues with 1 or more ca certificates")
}
if hasErrors, !managed {
errors.append("There are issues with 1 or more ca certificates")
}
@ -294,7 +297,7 @@ class Site: Codable {
errors.append("Unable to create the site directory: \(error.localizedDescription)")
}
if managed && (try? getDNCredentials())?.invalid != false {
if managed, (try? getDNCredentials())?.invalid != false {
errors.append("Unable to fetch managed updates - please re-enroll the device")
}
@ -426,7 +429,7 @@ class DNCredentials: Codable {
}
// This class represents a site coming in from flutter, meant only to be saved and re-loaded as a proper Site
struct IncomingSite: Codable {
struct IncomingSite: Codable, @unchecked Sendable {
var name: String
var id: String
var staticHostmap: [String: StaticHosts]

View file

@ -25,17 +25,19 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
dnUpdater.updateAllLoop { site in
// Signal the site has changed in case the current site details screen is active
let container = self.sites?.getContainer(id: site.id)
if container != nil {
// Update references to the site with the new site config
container!.site = site
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
}
Task {
await dnUpdater.updateAllLoop { @MainActor site in
// Signal the site has changed in case the current site details screen is active
let container = self.sites?.getContainer(id: site.id)
if container != nil {
// Update references to the site with the new site config
container!.site = site
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
}
// Signal to the main screen to reload
self.ui?.invokeMethod("refreshSites", arguments: nil)
// Signal to the main screen to reload
self.ui?.invokeMethod("refreshSites", arguments: nil)
}
}
guard let controller = window?.rootViewController as? FlutterViewController else {
@ -86,8 +88,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if err != nil {
return result(
CallFailedError(
message: "Error while parsing certificate(s)", details: err!.localizedDescription
))
message: "Error while parsing certificate(s)", details: err!.localizedDescription))
}
return result(json)
@ -109,8 +110,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
return result(
CallFailedError(
message: "Error while verifying certificate and private key",
details: err!.localizedDescription
))
details: err!.localizedDescription))
}
return result(valid)
@ -122,8 +122,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if err != nil {
return result(
CallFailedError(
message: "Error while generating key pairs", details: err!.localizedDescription
))
message: "Error while generating key pairs", details: err!.localizedDescription))
}
return result(kp)
@ -241,8 +240,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
} catch {
return result(
CallFailedError(
message: "Could not start site", details: error.localizedDescription
))
message: "Could not start site", details: error.localizedDescription))
}
}
}

View file

@ -1,29 +1,33 @@
import Foundation
import os.log
class DNUpdater {
actor DNUpdater {
private let apiClient = APIClient()
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
func updateAll(onUpdate: @escaping (Site) -> Void) {
func updateAll(onUpdate: @Sendable @escaping (Site) -> Void) {
_ = SiteList { sites, _ in
guard let unwrappedSites = sites else {
// There was an error, let's bail.
return
}
// NEVPN seems to force us onto the main thread and we are about to make network calls that
// could block for a while. Push ourselves onto another thread to avoid blocking the UI.
Task.detached(priority: .userInitiated) {
sites?.values.forEach { site in
for site in unwrappedSites.values {
if site.connected == true {
// The vpn service is in charge of updating the currently connected site
return
}
self.updateSite(site: site, onUpdate: onUpdate)
await self.updateSite(site: site, onUpdate: onUpdate)
}
}
}
}
func updateAllLoop(onUpdate: @escaping (Site) -> Void) {
func updateAllLoop(onUpdate: @Sendable @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateAll(onUpdate: onUpdate)
}
@ -37,7 +41,7 @@ class DNUpdater {
timer.resume()
}
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) {
func updateSite(site: Site, onUpdate: sending @escaping (Site) -> Void) {
do {
if !site.managed {
return