mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-01-18 19:27:05 +00:00
e58078fa48
This fixes a few issues: 1) When updates are made, we will no longer create duplicate VPN profiles, rather we will update existing ones. 2) We will correctly update an existing profile when the site is running and an update is received, rather than attempting to create a new profile, which failed due to permissions errors. 3) We will always reload nebula, even if we can't successfully save the VPN profile. 4) The default polling interval of 15 minutes is restored (previously set to 30 seconds during testing). So far in manual testing I've confirmed that I do not lose the tunnel to my lighthouse even after the original 30 minute expiration of a certificate. This confirms that reloads are occurring correctly. Additionally, duplicate sites are not created when updates occur while the site is disconnected.
139 lines
4.3 KiB
Swift
139 lines
4.3 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
|
|
}
|
|
|
|
newSite?.save(manager: site.manager) { 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()
|
|
}
|
|
}
|