Move VPN manager logic into its own controller

This commit is contained in:
relikd
2020-06-28 16:31:11 +02:00
parent 3ed25c92cd
commit 710c617862
7 changed files with 145 additions and 164 deletions

View File

@@ -1,13 +1,9 @@
import UIKit
import NetworkExtension
let VPNConfigBundleIdentifier = "de.uni-bamberg.psi.AppCheck.VPN"
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var managerVPN: NETunnelProviderManager?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if UserDefaults.standard.bool(forKey: "kill_db") {
@@ -23,119 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
TestDataSource.load()
#endif
loadVPN { mgr in
self.managerVPN = mgr
self.postVPNState()
}
NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self)
NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self)
sync.start()
return true
}
@objc private func vpnStatusChanged(_ notification: Notification) {
postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid)
}
@objc private func didChangeDomainFilter() {
// Notify VPN extension about changes
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
session.status == .connected {
try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil)
}
}
func setProxyEnabled(_ newState: Bool) {
guard let mgr = self.managerVPN else {
self.createNewVPN { manager in
self.managerVPN = manager
self.setProxyEnabled(newState)
}
return
}
let state = mgr.isEnabled && (mgr.connection.status == .connected)
if state != newState {
self.updateVPN({ mgr.isEnabled = true }) {
newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel()
}
}
}
// MARK: VPN
private func createNewVPN(_ success: @escaping (_ manager: NETunnelProviderManager) -> Void) {
let mgr = NETunnelProviderManager()
mgr.localizedDescription = "AppCheck Monitor"
let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = VPNConfigBundleIdentifier
proto.serverAddress = "127.0.0.1"
mgr.protocolConfiguration = proto
mgr.isEnabled = true
mgr.saveToPreferences { error in
guard error == nil else {
self.postProcessedVPNState(.off)
//ErrorAlert(error!).presentIn(self.window?.rootViewController)
return
}
success(mgr)
}
}
private func loadVPN(_ finally: @escaping (_ manager: NETunnelProviderManager?) -> Void) {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
guard let mgrs = managers, mgrs.count > 0 else {
finally(nil)
return
}
for mgr in mgrs {
if let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) {
if proto.providerBundleIdentifier == VPNConfigBundleIdentifier {
finally(mgr)
return
}
}
}
finally(nil)
}
}
private func updateVPN(_ body: @escaping () -> Void, _ onSuccess: @escaping () -> Void) {
self.managerVPN?.loadFromPreferences { error in
guard error == nil else { return }
body()
self.managerVPN?.saveToPreferences { error in
guard error == nil else { return }
onSuccess()
}
}
}
private func postVPNState() {
guard let mgr = self.managerVPN else {
self.postRawVPNState(.invalid)
return
}
mgr.loadFromPreferences { _ in
self.postRawVPNState(mgr.connection.status)
}
}
// MARK: Notifications
private func postRawVPNState(_ origState: NEVPNStatus) {
let state: VPNState
switch origState {
case .connected: state = .on
case .connecting, .disconnecting, .reasserting: state = .inbetween
case .invalid, .disconnected: fallthrough
@unknown default: state = .off
}
postProcessedVPNState(state)
}
private func postProcessedVPNState(_ state: VPNState) {
currentVPNState = state
NotifyVPNStateChanged.post(state)
}
}

View File

@@ -1,5 +1,7 @@
import UIKit
let sync = SyncUpdate(periodic: 7)
class SyncUpdate {
private var lastSync: TimeInterval = 0
private var timer: Timer!
@@ -18,7 +20,7 @@ class SyncUpdate {
private(set) var tsLatest: Timestamp? // as set per user, not actual latest
init(periodic interval: TimeInterval) {
fileprivate init(periodic interval: TimeInterval) {
(filterType, tsEarliest, tsLatest) = Prefs.DateFilter.restrictions()
reloadRangeFromDB()

View File

@@ -1,8 +0,0 @@
import Foundation
var currentVPNState: VPNState = .off
let sync = SyncUpdate(periodic: 7)
public enum VPNState : Int {
case on = 1, inbetween, off
}

111
main/GlassVPN.swift Normal file
View File

@@ -0,0 +1,111 @@
import NetworkExtension
let GlassVPN = GlassVPNManager()
enum VPNState : Int { case on = 1, inbetween, off }
final class GlassVPNManager {
static let bundleIdentifier = "de.uni-bamberg.psi.AppCheck.VPN"
private var managerVPN: NETunnelProviderManager?
private(set) var state: VPNState = .off
fileprivate init() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
self.managerVPN = managers?.first {
($0.protocolConfiguration as? NETunnelProviderProtocol)?
.providerBundleIdentifier == GlassVPNManager.bundleIdentifier
}
guard let mgr = self.managerVPN else {
self.postRawVPNState(.invalid)
return
}
mgr.loadFromPreferences { _ in
self.postRawVPNState(mgr.connection.status)
}
}
NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self)
NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self)
}
func setEnabled(_ newState: Bool) {
guard let mgr = self.managerVPN else {
self.createNewVPN { manager in
self.managerVPN = manager
self.setEnabled(newState)
}
return
}
let state = mgr.isEnabled && (mgr.connection.status == .connected)
if state != newState {
self.updateVPN({ mgr.isEnabled = true }) {
newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel()
}
}
}
// MARK: - Notify callback
@objc private func vpnStatusChanged(_ notification: Notification) {
postRawVPNState((notification.object as? NETunnelProviderSession)?.status ?? .invalid)
}
@objc private func didChangeDomainFilter() {
// Notify VPN extension about changes
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
session.status == .connected {
try? session.sendProviderMessage("filter-update".data(using: .ascii)!, responseHandler: nil)
}
}
// MARK: - Manage configuration
private func createNewVPN(_ success: @escaping (_ manager: NETunnelProviderManager) -> Void) {
let mgr = NETunnelProviderManager()
mgr.localizedDescription = "AppCheck Monitor"
let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = GlassVPNManager.bundleIdentifier
proto.serverAddress = "127.0.0.1"
mgr.protocolConfiguration = proto
mgr.isEnabled = true
mgr.saveToPreferences { error in
guard error == nil else {
self.postProcessedVPNState(.off)
//ErrorAlert(error!).presentIn(self.window?.rootViewController)
return
}
success(mgr)
}
}
private func updateVPN(_ body: @escaping () -> Void, _ onSuccess: @escaping () -> Void) {
self.managerVPN?.loadFromPreferences { error in
guard error == nil else { return }
body()
self.managerVPN?.saveToPreferences { error in
guard error == nil else { return }
onSuccess()
}
}
}
// MARK: - Post Notifications
private func postRawVPNState(_ origState: NEVPNStatus) {
let state: VPNState
switch origState {
case .connected: state = .on
case .connecting, .disconnecting, .reasserting: state = .inbetween
case .invalid, .disconnected: fallthrough
@unknown default: state = .off
}
postProcessedVPNState(state)
}
private func postProcessedVPNState(_ state: VPNState) {
self.state = state
NotifyVPNStateChanged.post(state)
}
}

View File

@@ -10,32 +10,28 @@ class TVCSettings: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotifyVPNStateChanged.observe(call: #selector(vpnStateChanged(_:)), on: self)
changedState(currentVPNState)
NotifyDNSFilterChanged.observe(call: #selector(reloadDataSource), on: self)
reloadToggleState()
reloadDataSource()
NotifyVPNStateChanged.observe(call: #selector(reloadToggleState), on: self)
NotifyDNSFilterChanged.observe(call: #selector(reloadDataSource), on: self)
}
// MARK: - VPN Proxy Settings
@IBAction private func toggleVPNProxy(_ sender: UISwitch) {
appDelegate.setProxyEnabled(sender.isOn)
GlassVPN.setEnabled(sender.isOn)
}
@objc func vpnStateChanged(_ notification: Notification) {
changedState(notification.object as! VPNState)
}
func changedState(_ newState: VPNState) {
vpnToggle.isOn = (newState != .off)
vpnToggle.onTintColor = (newState == .inbetween ? .systemYellow : nil)
@objc private func reloadToggleState() {
vpnToggle.isOn = (GlassVPN.state != .off)
vpnToggle.onTintColor = (GlassVPN.state == .inbetween ? .systemYellow : nil)
}
// MARK: - Logging Filter
@objc func reloadDataSource() {
@objc private func reloadDataSource() {
let (blocked, ignored) = DomainFilter.counts()
cellDomainsIgnored.detailTextLabel?.text = "\(ignored) Domains"
cellDomainsBlocked.detailTextLabel?.text = "\(blocked) Domains"

View File

@@ -5,15 +5,31 @@ class TBCMain: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
NotifyVPNStateChanged.observe(call: #selector(vpnStateChanged(_:)), on: self)
changedState(currentVPNState)
reloadTabBarBadge()
NotifyVPNStateChanged.observe(call: #selector(reloadTabBarBadge), on: self)
if !Prefs.DidShowTutorial.Welcome {
self.perform(#selector(showWelcomeMessage), with: nil, afterDelay: 0.5)
}
}
@objc func showWelcomeMessage() {
@objc private func reloadTabBarBadge() {
let stateView = self.tabBar.items?.last
switch GlassVPN.state {
case .on: stateView?.badgeValue = ""
case .inbetween: stateView?.badgeValue = ""
case .off: stateView?.badgeValue = ""
}
if #available(iOS 10.0, *) {
switch GlassVPN.state {
case .on: stateView?.badgeColor = .systemGreen
case .inbetween: stateView?.badgeColor = .systemYellow
case .off: stateView?.badgeColor = .systemRed
}
}
}
@objc private func showWelcomeMessage() {
let x = TutorialSheet()
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
.h1("Welcome\n")
@@ -40,24 +56,4 @@ class TBCMain: UITabBarController {
Prefs.DidShowTutorial.Welcome = true
}
}
@objc func vpnStateChanged(_ notification: Notification) {
changedState(notification.object as! VPNState)
}
func changedState(_ newState: VPNState) {
let stateView = self.tabBar.items?.last
switch newState {
case .on: stateView?.badgeValue = ""
case .inbetween: stateView?.badgeValue = ""
case .off: stateView?.badgeValue = ""
}
if #available(iOS 10.0, *) {
switch newState {
case .on: stateView?.badgeColor = .systemGreen
case .inbetween: stateView?.badgeColor = .systemYellow
case .off: stateView?.badgeColor = .systemRed
}
}
}
}