Format swift code

This commit is contained in:
Caleb Jasik 2025-02-20 15:20:17 -06:00
parent f1306d82d4
commit e87b7809b4
No known key found for this signature in database
9 changed files with 1569 additions and 1491 deletions

View file

@ -5,13 +5,13 @@ let groupName = "group.net.defined.mobileNebula"
class KeyChain {
class func save(key: String, data: Data, managed: Bool) -> Bool {
var query: [String: Any] = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data,
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessGroup as String: groupName,
]
if (managed) {
if managed {
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
}
@ -22,10 +22,10 @@ class KeyChain {
class func load(key: String) -> Data? {
let query: [String: Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne,
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: groupName,
]
@ -42,8 +42,8 @@ class KeyChain {
class func delete(key: String) -> Bool {
let query: [String: Any] = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key,
kSecAttrAccessGroup as String: groupName,
]
@ -56,7 +56,9 @@ extension Data {
init<T>(from value: T) {
var value = value
var data = Data()
withUnsafePointer(to: &value, { (ptr: UnsafePointer<T>) -> Void in
withUnsafePointer(
to: &value,
{ (ptr: UnsafePointer<T>) -> Void in
data = Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
})
self.init(data)
@ -66,4 +68,3 @@ extension Data {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}

View file

@ -1,7 +1,7 @@
import NetworkExtension
import MobileNebula
import os.log
import NetworkExtension
import SwiftyJSON
import os.log
enum VPNStartError: Error {
case noManagers
@ -23,7 +23,6 @@ extension AppMessageError: LocalizedError {
}
}
class PacketTunnelProvider: NEPacketTunnelProvider {
private var networkMonitor: NWPathMonitor?
@ -34,7 +33,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var didSleep = false
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
// `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 {
@ -55,7 +54,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
do {
// Cannot use NETunnelProviderManager.loadAllFromPreferences() in earlier versions of iOS
// 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()
guard let foundManager = manager else {
throw VPNStartError.couldNotFindManager
@ -87,16 +88,19 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// Make sure our ip is routed to the tun device
var err: NSError?
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
if (err != nil) {
if err != nil {
throw err!
}
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(
addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
var routes: [NEIPv4Route] = [
NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)
]
// Add our unsafe routes
try _site.unsafeRoutes.forEach { unsafeRoute in
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
if (err != nil) {
if err != nil {
throw err!
}
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
@ -107,7 +111,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
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()
if nebulaErr != nil {
@ -122,18 +127,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func handleDNUpdate(newSite: Site) {
do {
self.site = newSite
try self.nebula?.reload(String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey())
try self.nebula?.reload(
String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey())
} catch {
log.error("Got an error while updating nebula \(error.localizedDescription, privacy: .public)")
log.error(
"Got an error while updating nebula \(error.localizedDescription, privacy: .public)")
}
}
//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) {
// nebula!.sleep()
// completionHandler()
// }
//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) {
// nebula!.sleep()
// completionHandler()
// }
private func findManager() async throws -> NETunnelProviderManager {
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol
@ -150,7 +157,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
throw VPNStartError.noProviderConfig
}
let id = mgrProviderConfig["id"] as? String
if (id == targetID) {
if id == targetID {
return manager
}
}
@ -170,7 +177,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
networkMonitor = nil
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
override func stopTunnel(
with reason: NEProviderStopReason, completionHandler: @escaping () -> Void
) {
nebula?.stop()
stopNetworkMonitor()
completionHandler()
@ -181,7 +190,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
if routeDescription != cachedRouteDescription {
// Don't bother to rebind if we don't have any gateways
if routeDescription != "" {
nebula?.rebind("network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")")
nebula?.rebind(
"network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")")
}
cachedRouteDescription = routeDescription
}
@ -189,7 +199,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
var str: [String] = []
endpoints.forEach{ endpoint in
endpoints.forEach { endpoint in
switch endpoint {
case let .hostPort(.ipv6(host), port):
str.append("[\(host)]:\(port)")
@ -222,7 +232,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
defer {
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)))
}
}
@ -244,8 +255,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
error = AppMessageError.unknownIPCType(command: call.command)
}
if (error != nil) {
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
if error != nil {
return try? JSONEncoder().encode(
IPCResponse.init(
type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
} else {
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
}
@ -259,13 +272,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func getHostInfo(args: JSON) -> (JSON?, Error?) {
var err: NSError?
let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
let res = nebula!.getHostInfo(
byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err)
return (JSON(res), err)
}
private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) {
var err: NSError?
let res = nebula!.setRemoteForTunnel(args["vpnIp"].string, addr: args["addr"].string, error: &err)
let res = nebula!.setRemoteForTunnel(
args["vpnIp"].string, addr: args["addr"].string, error: &err)
return (JSON(res), err)
}
@ -306,4 +321,3 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return nil
}
}

View file

@ -1,12 +1,12 @@
import NetworkExtension
import MobileNebula
import NetworkExtension
import SwiftyJSON
import os.log
let log = Logger(subsystem: "net.defined.mobileNebula", category: "Site")
enum SiteError: Error {
case nonConforming(site: [String : Any]?)
case nonConforming(site: [String: Any]?)
case noCertificate
case keyLoad
case keySave
@ -131,7 +131,7 @@ struct CertificateValidity: Codable {
}
}
let statusMap: Dictionary<NEVPNStatus, Bool> = [
let statusMap: [NEVPNStatus: Bool] = [
NEVPNStatus.invalid: false,
NEVPNStatus.disconnected: false,
NEVPNStatus.connecting: false,
@ -140,7 +140,7 @@ let statusMap: Dictionary<NEVPNStatus, Bool> = [
NEVPNStatus.disconnecting: true,
]
let statusString: Dictionary<NEVPNStatus, String> = [
let statusString: [NEVPNStatus: String] = [
NEVPNStatus.invalid: "Invalid configuration",
NEVPNStatus.disconnected: "Disconnected",
NEVPNStatus.connecting: "Connecting...",
@ -156,7 +156,7 @@ class Site: Codable {
var id: String
// Stored in proto
var staticHostmap: Dictionary<String, StaticHosts>
var staticHostmap: [String: StaticHosts]
var unsafeRoutes: [UnsafeRoute]
var cert: CertificateInfo?
var ca: [CertificateInfo]
@ -244,18 +244,18 @@ class Site: Codable {
do {
let rawCert = incoming.cert
let rawDetails = MobileNebulaParseCerts(rawCert, &err)
if (err != nil) {
if err != nil {
throw err!
}
var certs: [CertificateInfo]
certs = try JSONDecoder().decode([CertificateInfo].self, from: rawDetails.data(using: .utf8)!)
if (certs.count == 0) {
if certs.count == 0 {
throw SiteError.noCertificate
}
cert = certs[0]
if (!cert!.validity.valid) {
if !cert!.validity.valid {
errors.append("Certificate is invalid: \(cert!.validity.reason)")
}
@ -266,19 +266,19 @@ class Site: Codable {
do {
let rawCa = incoming.ca
let rawCaDetails = MobileNebulaParseCerts(rawCa, &err)
if (err != nil) {
if err != nil {
throw err!
}
ca = try JSONDecoder().decode([CertificateInfo].self, from: rawCaDetails.data(using: .utf8)!)
var hasErrors = false
ca.forEach { cert in
if (!cert.validity.valid) {
if !cert.validity.valid {
hasErrors = true
}
}
if (hasErrors && !managed) {
if hasErrors && !managed {
errors.append("There are issues with 1 or more ca certificates")
}
@ -294,11 +294,11 @@ 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")
}
if (errors.isEmpty) {
if errors.isEmpty {
do {
let encoder = JSONEncoder()
let rawConfig = try encoder.encode(incoming)
@ -307,7 +307,7 @@ class Site: Codable {
var err: NSError?
MobileNebulaTestConfig(strConfig, key, &err)
if (err != nil) {
if err != nil {
throw err!
}
} catch {
@ -327,7 +327,7 @@ class Site: Codable {
}
func getDNCredentials() throws -> DNCredentials {
if (!managed) {
if !managed {
throw SiteError.unmanagedGetCredentials
}
@ -344,7 +344,7 @@ class Site: Codable {
let creds = try getDNCredentials()
creds.invalid = true
if (!(try creds.save(siteID: self.id))) {
if !(try creds.save(siteID: self.id)) {
throw SiteError.dnCredentialLoad
}
}
@ -353,7 +353,7 @@ class Site: Codable {
let creds = try getDNCredentials()
creds.invalid = false
if (!(try creds.save(siteID: self.id))) {
if !(try creds.save(siteID: self.id)) {
throw SiteError.dnCredentialSave
}
}
@ -429,7 +429,7 @@ class DNCredentials: Codable {
struct IncomingSite: Codable {
var name: String
var id: String
var staticHostmap: Dictionary<String, StaticHosts>
var staticHostmap: [String: StaticHosts]
var unsafeRoutes: [UnsafeRoute]?
var cert: String?
var ca: String?
@ -456,7 +456,10 @@ struct IncomingSite: Codable {
return try encoder.encode(config)
}
func save(manager: NETunnelProviderManager?, saveToManager: Bool = true, callback: @escaping (Error?) -> ()) {
func save(
manager: NETunnelProviderManager?, saveToManager: Bool = true,
callback: @escaping (Error?) -> Void
) {
let configPath: URL
do {
@ -469,15 +472,15 @@ struct IncomingSite: Codable {
log.notice("Saving to \(configPath, privacy: .public)")
do {
if (self.key != nil) {
if self.key != nil {
let data = self.key!.data(using: .utf8)
if (!KeyChain.save(key: "\(self.id).key", data: data!, managed: self.managed ?? false)) {
if !KeyChain.save(key: "\(self.id).key", data: data!, managed: self.managed ?? false) {
return callback(SiteError.keySave)
}
}
do {
if ((try self.dnCredentials?.save(siteID: self.id)) == false) {
if (try self.dnCredentials?.save(siteID: self.id)) == false {
return callback(SiteError.dnCredentialSave)
}
} catch {
@ -490,24 +493,25 @@ struct IncomingSite: Codable {
return callback(error)
}
#if targetEnvironment(simulator)
#if targetEnvironment(simulator)
// We are on a simulator and there is no NEVPNManager for us to interact with
callback(nil)
#else
#else
if saveToManager {
self.saveToManager(manager: manager, callback: callback)
} else {
callback(nil)
}
#endif
#endif
}
private func saveToManager(manager: NETunnelProviderManager?, callback: @escaping (Error?) -> ()) {
if (manager != nil) {
private func saveToManager(
manager: NETunnelProviderManager?, callback: @escaping (Error?) -> Void
) {
if manager != nil {
// We need to refresh our settings to properly update config
manager?.loadFromPreferences { error in
if (error != nil) {
if error != nil {
return callback(error)
}
@ -519,10 +523,13 @@ struct IncomingSite: Codable {
return finishSaveToManager(manager: NETunnelProviderManager(), callback: callback)
}
private func finishSaveToManager(manager: NETunnelProviderManager, callback: @escaping (Error?) -> ()) {
private func finishSaveToManager(
manager: NETunnelProviderManager, callback: @escaping (Error?) -> Void
) {
// Stuff our details in the protocol
let proto = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension";
let proto =
manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension"
// WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match
// managers in PacketTunnelProvider.findManager
proto.providerConfiguration = ["id": self.id]
@ -536,7 +543,7 @@ struct IncomingSite: Codable {
manager.localizedDescription = self.name
manager.isEnabled = true
manager.saveToPreferences{ error in
manager.saveToPreferences { error in
return callback(error)
}
}

View file

@ -6,9 +6,10 @@ class SiteList {
/// Gets the root directory that can be used to share files between the UI and VPN process. Does ensure the directory exists
static func getRootDir() throws -> URL {
let fileManager = FileManager.default
let rootDir = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.net.defined.mobileNebula")!
let rootDir = fileManager.containerURL(
forSecurityApplicationGroupIdentifier: "group.net.defined.mobileNebula")!
if (!fileManager.fileExists(atPath: rootDir.absoluteString)) {
if !fileManager.fileExists(atPath: rootDir.absoluteString) {
try fileManager.createDirectory(at: rootDir, withIntermediateDirectories: true)
}
@ -19,7 +20,7 @@ class SiteList {
static func getSitesDir() throws -> URL {
let fileManager = FileManager.default
let sitesDir = try getRootDir().appendingPathComponent("sites", isDirectory: true)
if (!fileManager.fileExists(atPath: sitesDir.absoluteString)) {
if !fileManager.fileExists(atPath: sitesDir.absoluteString) {
try fileManager.createDirectory(at: sitesDir, withIntermediateDirectories: true)
}
return sitesDir
@ -29,7 +30,7 @@ class SiteList {
static func getSiteDir(id: String, create: Bool = false) throws -> URL {
let fileManager = FileManager.default
let siteDir = try getSitesDir().appendingPathComponent(id, isDirectory: true)
if (create && !fileManager.fileExists(atPath: siteDir.absoluteString)) {
if create && !fileManager.fileExists(atPath: siteDir.absoluteString) {
try fileManager.createDirectory(at: siteDir, withIntermediateDirectories: true)
}
return siteDir
@ -37,39 +38,43 @@ class SiteList {
/// Gets the file that represents the site configuration, $rootDir/sites/$siteID/config.json
static func getSiteConfigFile(id: String, createDir: Bool) throws -> URL {
return try getSiteDir(id: id, create: createDir).appendingPathComponent("config", isDirectory: false).appendingPathExtension("json")
return try getSiteDir(id: id, create: createDir).appendingPathComponent(
"config", isDirectory: false
).appendingPathExtension("json")
}
/// Gets the file that represents the site log output, $rootDir/sites/$siteID/log
static func getSiteLogFile(id: String, createDir: Bool) throws -> URL {
return try getSiteDir(id: id, create: createDir).appendingPathComponent("logs", isDirectory: false)
return try getSiteDir(id: id, create: createDir).appendingPathComponent(
"logs", isDirectory: false)
}
init(completion: @escaping ([String: Site]?, Error?) -> ()) {
#if targetEnvironment(simulator)
init(completion: @escaping ([String: Site]?, Error?) -> Void) {
#if targetEnvironment(simulator)
SiteList.loadAllFromFS { sites, err in
if sites != nil {
self.sites = sites!
}
completion(sites, err)
}
#else
#else
SiteList.loadAllFromNETPM { sites, err in
if sites != nil {
self.sites = sites!
}
completion(sites, err)
}
#endif
#endif
}
private static func loadAllFromFS(completion: @escaping ([String: Site]?, Error?) -> ()) {
private static func loadAllFromFS(completion: @escaping ([String: Site]?, Error?) -> Void) {
let fileManager = FileManager.default
var siteDirs: [URL]
var sites = [String: Site]()
do {
siteDirs = try fileManager.contentsOfDirectory(at: getSitesDir(), includingPropertiesForKeys: nil)
siteDirs = try fileManager.contentsOfDirectory(
at: getSitesDir(), includingPropertiesForKeys: nil)
} catch {
completion(nil, error)
@ -78,7 +83,8 @@ class SiteList {
siteDirs.forEach { path in
do {
let site = try Site(path: path.appendingPathComponent("config").appendingPathExtension("json"))
let site = try Site(
path: path.appendingPathComponent("config").appendingPathExtension("json"))
sites[site.id] = site
} catch {
@ -91,15 +97,15 @@ class SiteList {
completion(sites, nil)
}
private static func loadAllFromNETPM(completion: @escaping ([String: Site]?, Error?) -> ()) {
private static func loadAllFromNETPM(completion: @escaping ([String: Site]?, Error?) -> Void) {
var sites = [String: Site]()
// dispatchGroup is used to ensure we have migrated all sites before returning them
// If there are no sites to migrate, there are never any entrants
let dispatchGroup = DispatchGroup()
NETunnelProviderManager.loadAllFromPreferences() { newManagers, err in
if (err != nil) {
NETunnelProviderManager.loadAllFromPreferences { newManagers, err in
if err != nil {
return completion(nil, err)
}

View file

@ -10,7 +10,8 @@ class APIClient {
init() {
let packageInfo = PackageInfo()
apiClient = MobileNebulaNewAPIClient("MobileNebula/\(packageInfo.getVersion()) (iOS \(packageInfo.getSystemVersion()))")!
apiClient = MobileNebulaNewAPIClient(
"MobileNebula/\(packageInfo.getVersion()) (iOS \(packageInfo.getSystemVersion()))")!
}
func enroll(code: String) throws -> IncomingSite {
@ -18,7 +19,9 @@ class APIClient {
return try decodeIncomingSite(jsonSite: res.site)
}
func tryUpdate(siteName: String, hostID: String, privateKey: String, counter: Int, trustedKeys: String) throws -> IncomingSite? {
func tryUpdate(
siteName: String, hostID: String, privateKey: String, counter: Int, trustedKeys: String
) throws -> IncomingSite? {
let res: MobileNebulaTryUpdateResult
do {
res = try apiClient.tryUpdate(
@ -29,14 +32,14 @@ class APIClient {
trustedKeys: trustedKeys)
} catch {
// type information from Go is not available, use string matching instead
if (error.localizedDescription == "invalid credentials") {
if error.localizedDescription == "invalid credentials" {
throw APIClientError.invalidCredentials
}
throw error
}
if (res.fetchedUpdate) {
if res.fetchedUpdate {
return try decodeIncomingSite(jsonSite: res.site)
}

View file

@ -1,8 +1,8 @@
import UIKit
import Flutter
import MobileNebula
import NetworkExtension
import SwiftyJSON
import UIKit
enum ChannelName {
static let vpn = "net.defined.mobileNebula/NebulaVpnService"
@ -19,18 +19,16 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
private var sites: Sites?
private var ui: FlutterMethodChannel?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> 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) {
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)
@ -47,7 +45,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
sites = Sites(messenger: controller.binaryMessenger)
ui = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger)
ui!.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
ui!.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch call.method {
case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result)
case "nebula.generateKeyPair": return self.nebulaGenerateKeyPair(result: result)
@ -62,11 +60,16 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
case "startSite": return self.startSite(call: call, result: result)
case "stopSite": return self.stopSite(call: call, result: result)
case "active.listHostmap": self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
case "active.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
case "active.listHostmap":
self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
case "active.listPendingHostmap":
self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
case "active.getHostInfo":
self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
case "active.setRemoteForTunnel":
self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
case "active.closeTunnel":
self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
default:
result(FlutterMethodNotImplemented)
@ -77,28 +80,39 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
}
func nebulaParseCerts(call: FlutterMethodCall, result: FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let certs = args["certs"] else { return result(MissingArgumentError(message: "certs is a required argument")) }
guard let args = call.arguments as? [String: String] else { return result(NoArgumentsError()) }
guard let certs = args["certs"] else {
return result(MissingArgumentError(message: "certs is a required argument"))
}
var err: NSError?
let json = MobileNebulaParseCerts(certs, &err)
if (err != nil) {
return result(CallFailedError(message: "Error while parsing certificate(s)", details: err!.localizedDescription))
if err != nil {
return result(
CallFailedError(
message: "Error while parsing certificate(s)", details: err!.localizedDescription))
}
return result(json)
}
func nebulaVerifyCertAndKey(call: FlutterMethodCall, result: FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let cert = args["cert"] else { return result(MissingArgumentError(message: "cert is a required argument")) }
guard let key = args["key"] else { return result(MissingArgumentError(message: "key is a required argument")) }
guard let args = call.arguments as? [String: String] else { return result(NoArgumentsError()) }
guard let cert = args["cert"] else {
return result(MissingArgumentError(message: "cert is a required argument"))
}
guard let key = args["key"] else {
return result(MissingArgumentError(message: "key is a required argument"))
}
var err: NSError?
var validd: ObjCBool = false
let valid = MobileNebulaVerifyCertAndKey(cert, key, &validd, &err)
if (err != nil) {
return result(CallFailedError(message: "Error while verifying certificate and private key", details: err!.localizedDescription))
if err != nil {
return result(
CallFailedError(
message: "Error while verifying certificate and private key",
details: err!.localizedDescription))
}
return result(valid)
@ -107,8 +121,10 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
func nebulaGenerateKeyPair(result: FlutterResult) {
var err: NSError?
let kp = MobileNebulaGenerateKeyPair(&err)
if (err != nil) {
return result(CallFailedError(message: "Error while generating key pairs", details: err!.localizedDescription))
if err != nil {
return result(
CallFailedError(
message: "Error while generating key pairs", details: err!.localizedDescription))
}
return result(kp)
@ -119,8 +135,10 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
var err: NSError?
let yaml = MobileNebulaRenderConfig(config, "<hidden>", &err)
if (err != nil) {
return result(CallFailedError(message: "Error while rendering config", details: err!.localizedDescription))
if err != nil {
return result(
CallFailedError(message: "Error while rendering config", details: err!.localizedDescription)
)
}
return result(yaml)
@ -134,21 +152,24 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
let oldSite = self.sites?.getSite(id: site.id)
site.save(manager: oldSite?.manager) { error in
if (error != nil) {
return result(CallFailedError(message: "Failed to enroll", details: error!.localizedDescription))
if error != nil {
return result(
CallFailedError(message: "Failed to enroll", details: error!.localizedDescription))
}
result(nil)
}
} catch {
return result(CallFailedError(message: "Error from DN api", details: error.localizedDescription))
return result(
CallFailedError(message: "Error from DN api", details: error.localizedDescription))
}
}
func listSites(result: @escaping FlutterResult) {
self.sites?.loadSites { (sites, err) -> () in
if (err != nil) {
return result(CallFailedError(message: "Failed to load site list", details: err!.localizedDescription))
self.sites?.loadSites { (sites, err) -> Void in
if err != nil {
return result(
CallFailedError(message: "Failed to load site list", details: err!.localizedDescription))
}
let encoder = JSONEncoder()
@ -162,8 +183,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
guard let id = call.arguments as? String else { return result(NoArgumentsError()) }
//TODO: stop the site if its running currently
self.sites?.deleteSite(id: id) { error in
if (error != nil) {
result(CallFailedError(message: "Failed to delete site", details: error!.localizedDescription))
if error != nil {
result(
CallFailedError(message: "Failed to delete site", details: error!.localizedDescription))
}
result(nil)
@ -180,8 +202,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
let oldSite = self.sites?.getSite(id: site.id)
site.save(manager: oldSite?.manager) { error in
if (error != nil) {
return result(CallFailedError(message: "Failed to save site", details: error!.localizedDescription))
if error != nil {
return result(
CallFailedError(message: "Failed to save site", details: error!.localizedDescription))
}
self.sites?.loadSites { _, _ in
@ -191,59 +214,67 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
}
func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
guard let args = call.arguments as? [String: String] else { return result(NoArgumentsError()) }
guard let id = args["id"] else {
return result(MissingArgumentError(message: "id is a required argument"))
}
#if targetEnvironment(simulator)
#if targetEnvironment(simulator)
let updater = self.sites?.getUpdater(id: id)
updater?.update(connected: true)
#else
#else
let container = self.sites?.getContainer(id: id)
let manager = container?.site.manager
manager?.loadFromPreferences{ error in
manager?.loadFromPreferences { error in
//TODO: Handle load error
// This is silly but we need to enable the site each time to avoid situations where folks have multiple sites
manager?.isEnabled = true
manager?.saveToPreferences{ error in
manager?.saveToPreferences { error in
//TODO: Handle load error
manager?.loadFromPreferences{ error in
manager?.loadFromPreferences { error in
//TODO: Handle load error
do {
container?.updater.startFunc = {() -> Void in
container?.updater.startFunc = { () -> Void in
return self.vpnRequest(command: "start", arguments: args, result: result)
}
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
} catch {
return result(CallFailedError(message: "Could not start site", details: error.localizedDescription))
return result(
CallFailedError(
message: "Could not start site", details: error.localizedDescription))
}
}
}
}
#endif
#endif
}
func stopSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) }
#if targetEnvironment(simulator)
guard let args = call.arguments as? [String: String] else { return result(NoArgumentsError()) }
guard let id = args["id"] else {
return result(MissingArgumentError(message: "id is a required argument"))
}
#if targetEnvironment(simulator)
let updater = self.sites?.getUpdater(id: id)
updater?.update(connected: false)
#else
#else
let manager = self.sites?.getSite(id: id)?.manager
manager?.loadFromPreferences{ error in
manager?.loadFromPreferences { error in
//TODO: Handle load error
manager?.connection.stopVPNTunnel()
return result(nil)
}
#endif
#endif
}
func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
guard let args = arguments as? Dictionary<String, Any> else { return result(NoArgumentsError()) }
guard let id = args["id"] as? String else { return result(MissingArgumentError(message: "id is a required argument")) }
guard let args = arguments as? [String: Any] else { return result(NoArgumentsError()) }
guard let id = args["id"] as? String else {
return result(MissingArgumentError(message: "id is a required argument"))
}
let container = sites?.getContainer(id: id)
if container == nil {
@ -258,7 +289,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
if let session = container!.site.manager?.connection as? NETunnelProviderSession {
do {
try session.sendProviderMessage(try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in
try session.sendProviderMessage(
try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))
) { data in
if data == nil {
return result(nil)
}
@ -272,7 +305,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
return result(res.message?.object)
}
return result(CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error"))
return result(
CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error"))
}
} catch {
return result(CallFailedError(message: error.localizedDescription))
@ -288,7 +322,10 @@ func MissingArgumentError(message: String, details: Error? = nil) -> FlutterErro
return FlutterError(code: "missingArgument", message: message, details: details)
}
func NoArgumentsError(message: String? = "no arguments were provided or could not be deserialized", details: Error? = nil) -> FlutterError {
func NoArgumentsError(
message: String? = "no arguments were provided or could not be deserialized",
details: Error? = nil
) -> FlutterError {
return FlutterError(code: "noArguments", message: message, details: details)
}

View file

@ -6,13 +6,13 @@ class DNUpdater {
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
func updateAll(onUpdate: @escaping (Site) -> Void) {
_ = SiteList { (sites, _) -> Void 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) {
if site.connected == true {
// The vpn service is in charge of updating the currently connected site
return
}
@ -23,23 +23,23 @@ class DNUpdater {
}
}
func updateAllLoop(onUpdate: @escaping (Site) -> ()) {
func updateAllLoop(onUpdate: @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateAll(onUpdate: onUpdate)
}
timer.resume()
}
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> ()) {
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> Void) {
timer.eventHandler = {
self.updateSite(site: site, onUpdate: onUpdate)
}
timer.resume()
}
func updateSite(site: Site, onUpdate: @escaping (Site) -> ()) {
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) {
do {
if (!site.managed) {
if !site.managed {
return
}
@ -55,7 +55,7 @@ class DNUpdater {
trustedKeys: credentials.trustedKeys
)
} catch (APIClientError.invalidCredentials) {
if (!credentials.invalid) {
if !credentials.invalid {
try site.invalidateDNCredentials()
log.notice("Invalidated credentials in site: \(site.name, privacy: .public)")
}
@ -64,10 +64,13 @@ class DNUpdater {
}
let siteManager = site.manager
let shouldSaveToManager = siteManager != nil || ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 17, minorVersion: 0, patchVersion: 0))
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) {
if error != nil {
self.log.error("failed to save update: \(error!.localizedDescription, privacy: .public)")
}
@ -75,13 +78,15 @@ class DNUpdater {
onUpdate(Site(incoming: newSite!))
}
if (credentials.invalid) {
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)")
log.error(
"Error while updating \(site.name, privacy: .public): \(error.localizedDescription, privacy: .public)"
)
}
}
}

View file

@ -2,11 +2,10 @@ import Foundation
class PackageInfo {
func getVersion() -> String {
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ??
"unknown"
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
if (buildNumber == nil) {
if buildNumber == nil {
return version
}
@ -14,9 +13,8 @@ class PackageInfo {
}
func getName() -> String {
return Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ??
Bundle.main.infoDictionary?["CFBundleName"] as? String ??
"Nebula"
return Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? Bundle.main
.infoDictionary?["CFBundleName"] as? String ?? "Nebula"
}
func getSystemVersion() -> String {

View file

@ -1,5 +1,5 @@
import NetworkExtension
import MobileNebula
import NetworkExtension
class SiteContainer {
var site: Site
@ -19,15 +19,15 @@ class Sites {
self.messenger = messenger
}
func loadSites(completion: @escaping ([String: Site]?, Error?) -> ()) {
func loadSites(completion: @escaping ([String: Site]?, Error?) -> Void) {
_ = SiteList { (sites, err) in
if (err != nil) {
if err != nil {
return completion(nil, err)
}
sites?.values.forEach{ site in
sites?.values.forEach { site in
var updater = self.containers[site.id]?.updater
if (updater != nil) {
if updater != nil {
updater!.setSite(site: site)
} else {
updater = SiteUpdater(messenger: self.messenger!, site: site)
@ -42,7 +42,7 @@ class Sites {
}
}
func deleteSite(id: String, callback: @escaping (Error?) -> ()) {
func deleteSite(id: String, callback: @escaping (Error?) -> Void) {
if let site = self.containers.removeValue(forKey: id) {
_ = KeyChain.delete(key: "\(site.site.id).dnCredentials")
_ = KeyChain.delete(key: "\(site.site.id).key")
@ -55,10 +55,10 @@ class Sites {
print("Failed to delete site from fs: \(error.localizedDescription)")
}
#if !targetEnvironment(simulator)
#if !targetEnvironment(simulator)
site.site.manager!.removeFromPreferences(completionHandler: callback)
return
#endif
#endif
}
// Nothing to remove
@ -79,8 +79,8 @@ class Sites {
}
class SiteUpdater: NSObject, FlutterStreamHandler {
private var eventSink: FlutterEventSink?;
private var eventChannel: FlutterEventChannel;
private var eventSink: FlutterEventSink?
private var eventChannel: FlutterEventChannel
private var site: Site
private var notification: Any?
public var startFunc: (() -> Void)?
@ -101,7 +101,8 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
self.configObserver = nil
}
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
eventChannel = FlutterEventChannel(
name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
self.site = site
super.init()
@ -123,17 +124,23 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
}
/// onListen is called when flutter code attaches an event listener
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events;
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink)
-> FlutterError?
{
eventSink = events
#if !targetEnvironment(simulator)
#if !targetEnvironment(simulator)
if site.manager == nil {
//TODO: The dn updater path seems to race to build a site that lacks a manager. The UI does not display this error
// and a another listen should occur and succeed.
return FlutterError(code: "Internal Error", message: "Flutter manager was not present", details: nil)
return FlutterError(
code: "Internal Error", message: "Flutter manager was not present", details: nil)
}
self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection , queue: nil) { n in
self.notification = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection,
queue: nil
) { n in
let oldConnected = self.site.connected
self.site.status = statusString[self.site.manager!.connection.status]
self.site.connected = statusMap[self.site.manager!.connection.status]
@ -146,13 +153,13 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
self.update(connected: self.site.connected!)
}
#endif
#endif
return nil
}
/// onCancel is called when the flutter listener stops listening
func onCancel(withArguments arguments: Any?) -> FlutterError? {
if (self.notification != nil) {
if self.notification != nil {
NotificationCenter.default.removeObserver(self.notification!)
}
return nil
@ -160,7 +167,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
/// update is a way to send information to the flutter listener and generally should not be used directly
func update(connected: Bool, replaceSite: Site? = nil) {
if (replaceSite != nil) {
if replaceSite != nil {
site = replaceSite!
}
site.connected = connected