mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-01-18 19:27:05 +00:00
87c16ea95c
In older versions of iOS, it's not possible to call `NETunnelProviderManager.loadAllFromPreferences()` from inside the network extension process. We were seeing `NETunnelProviderManager objects cannot be instantiated from NEProvider processes` errors in iOS 16. It's unclear exactly when the change happened to allow it, but as far as we can tell it was in iOS 17. To Test: 1. On a real device running iOS 16, ensure that enrolling as a Managed Nebula host works correctly. 2. Start the site. 3. Update the host in the admin panel and wait at least 15 minutes for a `checkForUpdate` from the mobile client. You should get a `Host renewed` audit log for the host. 4. Verify that there's a log for "Reloading Nebula" in the mobile host, and that it has an up-to-date config.
142 lines
4.5 KiB
Swift
142 lines
4.5 KiB
Swift
import Foundation
|
|
import os.log
|
|
|
|
class 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) -> ()) {
|
|
_ = SiteList{ (sites, _) -> () in
|
|
// 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
|
|
if (site.connected == true) {
|
|
// The vpn service is in charge of updating the currently connected site
|
|
return
|
|
}
|
|
|
|
self.updateSite(site: site, onUpdate: onUpdate)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateAllLoop(onUpdate: @escaping (Site) -> ()) {
|
|
timer.eventHandler = {
|
|
self.updateAll(onUpdate: onUpdate)
|
|
}
|
|
timer.resume()
|
|
}
|
|
|
|
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> ()) {
|
|
timer.eventHandler = {
|
|
self.updateSite(site: site, onUpdate: onUpdate)
|
|
}
|
|
timer.resume()
|
|
}
|
|
|
|
func updateSite(site: Site, onUpdate: @escaping (Site) -> ()) {
|
|
do {
|
|
if (!site.managed) {
|
|
return
|
|
}
|
|
|
|
let credentials = try site.getDNCredentials()
|
|
|
|
let newSite: IncomingSite?
|
|
do {
|
|
newSite = try apiClient.tryUpdate(
|
|
siteName: site.name,
|
|
hostID: credentials.hostID,
|
|
privateKey: credentials.privateKey,
|
|
counter: credentials.counter,
|
|
trustedKeys: credentials.trustedKeys
|
|
)
|
|
} catch (APIClientError.invalidCredentials) {
|
|
if (!credentials.invalid) {
|
|
try site.invalidateDNCredentials()
|
|
log.notice("Invalidated credentials in site: \(site.name, privacy: .public)")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
let siteManager = site.manager
|
|
let shouldSaveToManager = siteManager != nil || ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0))
|
|
|
|
newSite?.save(manager: site.manager, saveToManager: shouldSaveToManager) { error in
|
|
if (error != nil) {
|
|
self.log.error("failed to save update: \(error!.localizedDescription, privacy: .public)")
|
|
}
|
|
|
|
// reload nebula even if we couldn't save the vpn profile
|
|
onUpdate(Site(incoming: newSite!))
|
|
}
|
|
|
|
if (credentials.invalid) {
|
|
try site.validateDNCredentials()
|
|
log.notice("Revalidated credentials in site \(site.name, privacy: .public)")
|
|
}
|
|
|
|
} catch {
|
|
log.error("Error while updating \(site.name, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
|
|
class RepeatingTimer {
|
|
|
|
let timeInterval: TimeInterval
|
|
|
|
init(timeInterval: TimeInterval) {
|
|
self.timeInterval = timeInterval
|
|
}
|
|
|
|
private lazy var timer: DispatchSourceTimer = {
|
|
let t = DispatchSource.makeTimerSource()
|
|
t.schedule(deadline: .now(), repeating: self.timeInterval)
|
|
t.setEventHandler(handler: { [weak self] in
|
|
self?.eventHandler?()
|
|
})
|
|
return t
|
|
}()
|
|
|
|
var eventHandler: (() -> Void)?
|
|
|
|
private enum State {
|
|
case suspended
|
|
case resumed
|
|
}
|
|
|
|
private var state: State = .suspended
|
|
|
|
deinit {
|
|
timer.setEventHandler {}
|
|
timer.cancel()
|
|
/*
|
|
If the timer is suspended, calling cancel without resuming
|
|
triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
|
|
*/
|
|
resume()
|
|
eventHandler = nil
|
|
}
|
|
|
|
func resume() {
|
|
if state == .resumed {
|
|
return
|
|
}
|
|
state = .resumed
|
|
timer.resume()
|
|
}
|
|
|
|
func suspend() {
|
|
if state == .suspended {
|
|
return
|
|
}
|
|
state = .suspended
|
|
timer.suspend()
|
|
}
|
|
}
|