mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-02-19 09:45:26 +00:00
swiftformat .
This commit is contained in:
parent
02e8c5bf35
commit
7c4a1a5faa
9 changed files with 411 additions and 423 deletions
|
@ -5,31 +5,31 @@ let groupName = "group.net.defined.mobileNebula"
|
||||||
class KeyChain {
|
class KeyChain {
|
||||||
class func save(key: String, data: Data, managed: Bool) -> Bool {
|
class func save(key: String, data: Data, managed: Bool) -> Bool {
|
||||||
var query: [String: Any] = [
|
var query: [String: Any] = [
|
||||||
kSecClass as String : kSecClassGenericPassword as String,
|
kSecClass as String: kSecClassGenericPassword as String,
|
||||||
kSecAttrAccount as String : key,
|
kSecAttrAccount as String: key,
|
||||||
kSecValueData as String : data,
|
kSecValueData as String: data,
|
||||||
kSecAttrAccessGroup as String: groupName,
|
kSecAttrAccessGroup as String: groupName,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (managed) {
|
if managed {
|
||||||
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to delete an existing key to allow for an overwrite
|
// Attempt to delete an existing key to allow for an overwrite
|
||||||
_ = self.delete(key: key)
|
_ = delete(key: key)
|
||||||
return SecItemAdd(query as CFDictionary, nil) == 0
|
return SecItemAdd(query as CFDictionary, nil) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
class func load(key: String) -> Data? {
|
class func load(key: String) -> Data? {
|
||||||
let query: [String: Any] = [
|
let query: [String: Any] = [
|
||||||
kSecClass as String : kSecClassGenericPassword,
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
kSecAttrAccount as String : key,
|
kSecAttrAccount as String: key,
|
||||||
kSecReturnData as String : kCFBooleanTrue!,
|
kSecReturnData as String: kCFBooleanTrue!,
|
||||||
kSecMatchLimit as String : kSecMatchLimitOne,
|
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||||
kSecAttrAccessGroup as String: groupName,
|
kSecAttrAccessGroup as String: groupName,
|
||||||
]
|
]
|
||||||
|
|
||||||
var dataTypeRef: AnyObject? = nil
|
var dataTypeRef: AnyObject?
|
||||||
|
|
||||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||||
|
|
||||||
|
@ -41,29 +41,27 @@ class KeyChain {
|
||||||
}
|
}
|
||||||
|
|
||||||
class func delete(key: String) -> Bool {
|
class func delete(key: String) -> Bool {
|
||||||
let query: [String: Any] = [
|
let query: [String: Any] = [
|
||||||
kSecClass as String : kSecClassGenericPassword as String,
|
kSecClass as String: kSecClassGenericPassword as String,
|
||||||
kSecAttrAccount as String : key,
|
kSecAttrAccount as String: key,
|
||||||
kSecAttrAccessGroup as String: groupName,
|
kSecAttrAccessGroup as String: groupName,
|
||||||
]
|
]
|
||||||
|
|
||||||
return SecItemDelete(query as CFDictionary) == 0
|
return SecItemDelete(query as CFDictionary) == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Data {
|
extension Data {
|
||||||
|
|
||||||
init<T>(from value: T) {
|
init<T>(from value: T) {
|
||||||
var value = value
|
var value = value
|
||||||
var data = Data()
|
var data = Data()
|
||||||
withUnsafePointer(to: &value, { (ptr: UnsafePointer<T>) -> Void in
|
withUnsafePointer(to: &value) { (ptr: UnsafePointer<T>) in
|
||||||
data = Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
|
data = Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
|
||||||
})
|
}
|
||||||
self.init(data)
|
self.init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func to<T>(type: T.Type) -> T {
|
func to<T>(type _: T.Type) -> T {
|
||||||
return self.withUnsafeBytes { $0.load(as: T.self) }
|
return withUnsafeBytes { $0.load(as: T.self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
|
import NetworkExtension
|
||||||
import os.log
|
import os.log
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
|
@ -17,13 +17,12 @@ enum AppMessageError: Error {
|
||||||
extension AppMessageError: LocalizedError {
|
extension AppMessageError: LocalizedError {
|
||||||
public var description: String? {
|
public var description: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknownIPCType(let command):
|
case let .unknownIPCType(command):
|
||||||
return NSLocalizedString("Unknown IPC message type \(String(command))", comment: "")
|
return NSLocalizedString("Unknown IPC message type \(String(command))", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private var networkMonitor: NWPathMonitor?
|
private var networkMonitor: NWPathMonitor?
|
||||||
|
|
||||||
|
@ -34,7 +33,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private var didSleep = false
|
private var didSleep = false
|
||||||
private var cachedRouteDescription: String?
|
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
|
// 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
|
// `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 {
|
if options?["expectStart"] != nil {
|
||||||
|
@ -56,27 +55,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
// Cannot use NETunnelProviderManager.loadAllFromPreferences() in earlier versions of iOS
|
// Cannot use NETunnelProviderManager.loadAllFromPreferences() in earlier versions of iOS
|
||||||
// TODO: Remove else once we drop support for iOS 16
|
// 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()
|
manager = try await findManager()
|
||||||
guard let foundManager = manager else {
|
guard let foundManager = manager else {
|
||||||
throw VPNStartError.couldNotFindManager
|
throw VPNStartError.couldNotFindManager
|
||||||
}
|
}
|
||||||
self.site = try Site(manager: foundManager)
|
site = try Site(manager: foundManager)
|
||||||
} else {
|
} else {
|
||||||
// This does not save the manager with the site, which means we cannot update the
|
// This does not save the manager with the site, which means we cannot update the
|
||||||
// vpn profile name when updates happen (rare).
|
// vpn profile name when updates happen (rare).
|
||||||
self.site = try Site(proto: self.protocolConfiguration as! NETunnelProviderProtocol)
|
site = try Site(proto: protocolConfiguration as! NETunnelProviderProtocol)
|
||||||
}
|
}
|
||||||
config = try self.site!.getConfig()
|
config = try site!.getConfig()
|
||||||
} catch {
|
} catch {
|
||||||
//TODO: need a way to notify the app
|
// TODO: need a way to notify the app
|
||||||
self.log.error("Failed to render config from vpn object")
|
log.error("Failed to render config from vpn object")
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
let _site = self.site!
|
let _site = site!
|
||||||
key = try _site.getKey()
|
key = try _site.getKey()
|
||||||
|
|
||||||
guard let fileDescriptor = self.tunnelFileDescriptor else {
|
guard let fileDescriptor = tunnelFileDescriptor else {
|
||||||
throw VPNStartError.noTunFileDescriptor
|
throw VPNStartError.noTunFileDescriptor
|
||||||
}
|
}
|
||||||
let tunFD = Int(fileDescriptor)
|
let tunFD = Int(fileDescriptor)
|
||||||
|
@ -87,7 +86,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
// Make sure our ip is routed to the tun device
|
// Make sure our ip is routed to the tun device
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
||||||
|
@ -96,7 +95,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
// Add our unsafe routes
|
// Add our unsafe routes
|
||||||
try _site.unsafeRoutes.forEach { unsafeRoute in
|
try _site.unsafeRoutes.forEach { unsafeRoute in
|
||||||
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
||||||
|
@ -105,38 +104,38 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
|
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
|
||||||
tunnelNetworkSettings.mtu = _site.mtu as NSNumber
|
tunnelNetworkSettings.mtu = _site.mtu as NSNumber
|
||||||
|
|
||||||
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
try await setTunnelNetworkSettings(tunnelNetworkSettings)
|
||||||
var nebulaErr: NSError?
|
var nebulaErr: NSError?
|
||||||
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr)
|
nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, site!.logFile, tunFD, &nebulaErr)
|
||||||
self.startNetworkMonitor()
|
startNetworkMonitor()
|
||||||
|
|
||||||
if nebulaErr != nil {
|
if nebulaErr != nil {
|
||||||
self.log.error("We had an error starting up: \(nebulaErr, privacy: .public)")
|
log.error("We had an error starting up: \(nebulaErr, privacy: .public)")
|
||||||
throw nebulaErr!
|
throw nebulaErr!
|
||||||
}
|
}
|
||||||
|
|
||||||
self.nebula!.start()
|
nebula!.start()
|
||||||
self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate)
|
dnUpdater.updateSingleLoop(site: site!, onUpdate: handleDNUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleDNUpdate(newSite: Site) {
|
private func handleDNUpdate(newSite: Site) {
|
||||||
do {
|
do {
|
||||||
self.site = newSite
|
site = newSite
|
||||||
try self.nebula?.reload(String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey())
|
try nebula?.reload(String(data: newSite.getConfig(), encoding: .utf8), key: newSite.getKey())
|
||||||
|
|
||||||
} catch {
|
} 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
|
// 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) {
|
// override func sleep(completionHandler: @escaping () -> Void) {
|
||||||
// nebula!.sleep()
|
// nebula!.sleep()
|
||||||
// completionHandler()
|
// completionHandler()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private func findManager() async throws -> NETunnelProviderManager {
|
private func findManager() async throws -> NETunnelProviderManager {
|
||||||
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol
|
let targetProtoConfig = protocolConfiguration as? NETunnelProviderProtocol
|
||||||
guard let targetProviderConfig = targetProtoConfig?.providerConfiguration else {
|
guard let targetProviderConfig = targetProtoConfig?.providerConfiguration else {
|
||||||
throw VPNStartError.noProviderConfig
|
throw VPNStartError.noProviderConfig
|
||||||
}
|
}
|
||||||
|
@ -150,7 +149,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
throw VPNStartError.noProviderConfig
|
throw VPNStartError.noProviderConfig
|
||||||
}
|
}
|
||||||
let id = mgrProviderConfig["id"] as? String
|
let id = mgrProviderConfig["id"] as? String
|
||||||
if (id == targetID) {
|
if id == targetID {
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,16 +160,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private func startNetworkMonitor() {
|
private func startNetworkMonitor() {
|
||||||
networkMonitor = NWPathMonitor()
|
networkMonitor = NWPathMonitor()
|
||||||
networkMonitor!.pathUpdateHandler = self.pathUpdate
|
networkMonitor!.pathUpdateHandler = pathUpdate
|
||||||
networkMonitor!.start(queue: DispatchQueue(label: "NetworkMonitor"))
|
networkMonitor!.start(queue: DispatchQueue(label: "NetworkMonitor"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopNetworkMonitor() {
|
private func stopNetworkMonitor() {
|
||||||
self.networkMonitor?.cancel()
|
networkMonitor?.cancel()
|
||||||
networkMonitor = nil
|
networkMonitor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
nebula?.stop()
|
nebula?.stop()
|
||||||
stopNetworkMonitor()
|
stopNetworkMonitor()
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
@ -189,14 +188,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
|
private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String {
|
||||||
var str: [String] = []
|
var str: [String] = []
|
||||||
endpoints.forEach{ endpoint in
|
for endpoint in endpoints {
|
||||||
switch endpoint {
|
switch endpoint {
|
||||||
case let .hostPort(.ipv6(host), port):
|
case let .hostPort(.ipv6(host), port):
|
||||||
str.append("[\(host)]:\(port)")
|
str.append("[\(host)]:\(port)")
|
||||||
case let .hostPort(.ipv4(host), port):
|
case let .hostPort(.ipv4(host), port):
|
||||||
str.append("\(host):\(port)")
|
str.append("\(host):\(port)")
|
||||||
default:
|
default:
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,39 +214,38 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
// start command has special treatment due to needing to call two completers
|
// start command has special treatment due to needing to call two completers
|
||||||
if call.command == "start" {
|
if call.command == "start" {
|
||||||
do {
|
do {
|
||||||
try await self.start()
|
try await start()
|
||||||
// No response data, this is expected on a clean start
|
// No response data, this is expected on a clean start
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
|
return try? JSONEncoder().encode(IPCResponse(type: .success, message: nil))
|
||||||
} catch {
|
} catch {
|
||||||
defer {
|
defer {
|
||||||
self.cancelTunnelWithError(error)
|
self.cancelTunnelWithError(error)
|
||||||
}
|
}
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error.localizedDescription)))
|
return try? JSONEncoder().encode(IPCResponse(type: .error, message: JSON(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nebula == nil {
|
if nebula == nil {
|
||||||
// Respond with an empty success message in the event a command comes in before we've truly started
|
// Respond with an empty success message in the event a command comes in before we've truly started
|
||||||
log.warning("Received command but do not have a nebula instance")
|
log.warning("Received command but do not have a nebula instance")
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
|
return try? JSONEncoder().encode(IPCResponse(type: .success, message: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: try catch over all this
|
// TODO: try catch over all this
|
||||||
switch call.command {
|
switch call.command {
|
||||||
case "listHostmap": (data, error) = listHostmap(pending: false)
|
case "listHostmap": (data, error) = listHostmap(pending: false)
|
||||||
case "listPendingHostmap": (data, error) = listHostmap(pending: true)
|
case "listPendingHostmap": (data, error) = listHostmap(pending: true)
|
||||||
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
|
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
|
||||||
case "setRemoteForTunnel": (data, error) = setRemoteForTunnel(args: call.arguments!)
|
case "setRemoteForTunnel": (data, error) = setRemoteForTunnel(args: call.arguments!)
|
||||||
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
|
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error = AppMessageError.unknownIPCType(command: call.command)
|
error = AppMessageError.unknownIPCType(command: call.command)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
|
return try? JSONEncoder().encode(IPCResponse(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
|
||||||
} else {
|
} else {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
|
return try? JSONEncoder().encode(IPCResponse(type: .success, message: data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +279,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
_ = strcpy($0, "com.apple.net.utun_control")
|
_ = strcpy($0, "com.apple.net.utun_control")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for fd: Int32 in 0...1024 {
|
for fd: Int32 in 0 ... 1024 {
|
||||||
var addr = sockaddr_ctl()
|
var addr = sockaddr_ctl()
|
||||||
var ret: Int32 = -1
|
var ret: Int32 = -1
|
||||||
var len = socklen_t(MemoryLayout.size(ofValue: addr))
|
var len = socklen_t(MemoryLayout.size(ofValue: addr))
|
||||||
|
@ -306,4 +304,3 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
import SwiftyJSON
|
import NetworkExtension
|
||||||
import os.log
|
import os.log
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
let log = Logger(subsystem: "net.defined.mobileNebula", category: "Site")
|
let log = Logger(subsystem: "net.defined.mobileNebula", category: "Site")
|
||||||
|
|
||||||
enum SiteError: Error {
|
enum SiteError: Error {
|
||||||
case nonConforming(site: [String : Any]?)
|
case nonConforming(site: [String: Any]?)
|
||||||
case noCertificate
|
case noCertificate
|
||||||
case keyLoad
|
case keyLoad
|
||||||
case keySave
|
case keySave
|
||||||
|
@ -21,7 +21,7 @@ enum SiteError: Error {
|
||||||
extension SiteError: CustomStringConvertible {
|
extension SiteError: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .nonConforming(let site):
|
case let .nonConforming(site):
|
||||||
return String("Non-conforming site \(String(describing: site))")
|
return String("Non-conforming site \(String(describing: site))")
|
||||||
case .noCertificate:
|
case .noCertificate:
|
||||||
return "No certificate found"
|
return "No certificate found"
|
||||||
|
@ -35,20 +35,20 @@ extension SiteError: CustomStringConvertible {
|
||||||
return "failed to find dn credentials in keychain"
|
return "failed to find dn credentials in keychain"
|
||||||
case .dnCredentialSave:
|
case .dnCredentialSave:
|
||||||
return "failed to store dn credentials in keychain"
|
return "failed to store dn credentials in keychain"
|
||||||
case .unexpected(_):
|
case .unexpected:
|
||||||
return "An unexpected error occurred."
|
return "An unexpected error occurred."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IPCResponseType: String, Codable {
|
enum IPCResponseType: String, Codable {
|
||||||
case error = "error"
|
case error
|
||||||
case success = "success"
|
case success
|
||||||
}
|
}
|
||||||
|
|
||||||
class IPCResponse: Codable {
|
class IPCResponse: Codable {
|
||||||
var type: IPCResponseType
|
var type: IPCResponseType
|
||||||
//TODO: change message to data?
|
// TODO: change message to data?
|
||||||
var message: JSON?
|
var message: JSON?
|
||||||
|
|
||||||
init(type: IPCResponseType, message: JSON?) {
|
init(type: IPCResponseType, message: JSON?) {
|
||||||
|
@ -131,7 +131,7 @@ struct CertificateValidity: Codable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusMap: Dictionary<NEVPNStatus, Bool> = [
|
let statusMap: [NEVPNStatus: Bool] = [
|
||||||
NEVPNStatus.invalid: false,
|
NEVPNStatus.invalid: false,
|
||||||
NEVPNStatus.disconnected: false,
|
NEVPNStatus.disconnected: false,
|
||||||
NEVPNStatus.connecting: false,
|
NEVPNStatus.connecting: false,
|
||||||
|
@ -140,7 +140,7 @@ let statusMap: Dictionary<NEVPNStatus, Bool> = [
|
||||||
NEVPNStatus.disconnecting: true,
|
NEVPNStatus.disconnecting: true,
|
||||||
]
|
]
|
||||||
|
|
||||||
let statusString: Dictionary<NEVPNStatus, String> = [
|
let statusString: [NEVPNStatus: String] = [
|
||||||
NEVPNStatus.invalid: "Invalid configuration",
|
NEVPNStatus.invalid: "Invalid configuration",
|
||||||
NEVPNStatus.disconnected: "Disconnected",
|
NEVPNStatus.disconnected: "Disconnected",
|
||||||
NEVPNStatus.connecting: "Connecting...",
|
NEVPNStatus.connecting: "Connecting...",
|
||||||
|
@ -156,7 +156,7 @@ class Site: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
|
|
||||||
// Stored in proto
|
// Stored in proto
|
||||||
var staticHostmap: Dictionary<String, StaticHosts>
|
var staticHostmap: [String: StaticHosts]
|
||||||
var unsafeRoutes: [UnsafeRoute]
|
var unsafeRoutes: [UnsafeRoute]
|
||||||
var cert: CertificateInfo?
|
var cert: CertificateInfo?
|
||||||
var ca: [CertificateInfo]
|
var ca: [CertificateInfo]
|
||||||
|
@ -166,7 +166,7 @@ class Site: Codable {
|
||||||
var cipher: String
|
var cipher: String
|
||||||
var sortKey: Int
|
var sortKey: Int
|
||||||
var logVerbosity: String
|
var logVerbosity: String
|
||||||
var connected: Bool? //TODO: active is a better name
|
var connected: Bool? // TODO: active is a better name
|
||||||
var status: String?
|
var status: String?
|
||||||
var logFile: String?
|
var logFile: String?
|
||||||
var managed: Bool
|
var managed: Bool
|
||||||
|
@ -186,12 +186,12 @@ class Site: Codable {
|
||||||
|
|
||||||
/// Creates a new site from a vpn manager instance. Mainly used by the UI. A manager is required to be able to edit the system profile
|
/// Creates a new site from a vpn manager instance. Mainly used by the UI. A manager is required to be able to edit the system profile
|
||||||
convenience init(manager: NETunnelProviderManager) throws {
|
convenience init(manager: NETunnelProviderManager) throws {
|
||||||
//TODO: Throw an error and have Sites delete the site, notify the user instead of using !
|
// TODO: Throw an error and have Sites delete the site, notify the user instead of using !
|
||||||
let proto = manager.protocolConfiguration as! NETunnelProviderProtocol
|
let proto = manager.protocolConfiguration as! NETunnelProviderProtocol
|
||||||
try self.init(proto: proto)
|
try self.init(proto: proto)
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self.connected = statusMap[manager.connection.status]
|
connected = statusMap[manager.connection.status]
|
||||||
self.status = statusString[manager.connection.status]
|
status = statusString[manager.connection.status]
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(proto: NETunnelProviderProtocol) throws {
|
convenience init(proto: NETunnelProviderProtocol) throws {
|
||||||
|
@ -202,7 +202,7 @@ class Site: Codable {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
let incoming = try decoder.decode(IncomingSite.self, from: config)
|
let incoming = try decoder.decode(IncomingSite.self, from: config)
|
||||||
self.init(incoming: incoming)
|
self.init(incoming: incoming)
|
||||||
self.needsToMigrateToFS = true
|
needsToMigrateToFS = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,18 +244,18 @@ class Site: Codable {
|
||||||
do {
|
do {
|
||||||
let rawCert = incoming.cert
|
let rawCert = incoming.cert
|
||||||
let rawDetails = MobileNebulaParseCerts(rawCert, &err)
|
let rawDetails = MobileNebulaParseCerts(rawCert, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
|
|
||||||
var certs: [CertificateInfo]
|
var certs: [CertificateInfo]
|
||||||
|
|
||||||
certs = try JSONDecoder().decode([CertificateInfo].self, from: rawDetails.data(using: .utf8)!)
|
certs = try JSONDecoder().decode([CertificateInfo].self, from: rawDetails.data(using: .utf8)!)
|
||||||
if (certs.count == 0) {
|
if certs.count == 0 {
|
||||||
throw SiteError.noCertificate
|
throw SiteError.noCertificate
|
||||||
}
|
}
|
||||||
cert = certs[0]
|
cert = certs[0]
|
||||||
if (!cert!.validity.valid) {
|
if !cert!.validity.valid {
|
||||||
errors.append("Certificate is invalid: \(cert!.validity.reason)")
|
errors.append("Certificate is invalid: \(cert!.validity.reason)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,19 +266,19 @@ class Site: Codable {
|
||||||
do {
|
do {
|
||||||
let rawCa = incoming.ca
|
let rawCa = incoming.ca
|
||||||
let rawCaDetails = MobileNebulaParseCerts(rawCa, &err)
|
let rawCaDetails = MobileNebulaParseCerts(rawCa, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
ca = try JSONDecoder().decode([CertificateInfo].self, from: rawCaDetails.data(using: .utf8)!)
|
ca = try JSONDecoder().decode([CertificateInfo].self, from: rawCaDetails.data(using: .utf8)!)
|
||||||
|
|
||||||
var hasErrors = false
|
var hasErrors = false
|
||||||
ca.forEach { cert in
|
for cert in ca {
|
||||||
if (!cert.validity.valid) {
|
if !cert.validity.valid {
|
||||||
hasErrors = true
|
hasErrors = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasErrors && !managed) {
|
if hasErrors && !managed {
|
||||||
errors.append("There are issues with 1 or more ca certificates")
|
errors.append("There are issues with 1 or more ca certificates")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,17 +288,17 @@ class Site: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
logFile = try SiteList.getSiteLogFile(id: self.id, createDir: true).path
|
logFile = try SiteList.getSiteLogFile(id: id, createDir: true).path
|
||||||
} catch {
|
} catch {
|
||||||
logFile = nil
|
logFile = nil
|
||||||
errors.append("Unable to create the site directory: \(error.localizedDescription)")
|
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")
|
errors.append("Unable to fetch managed updates - please re-enroll the device")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.isEmpty) {
|
if errors.isEmpty {
|
||||||
do {
|
do {
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
let rawConfig = try encoder.encode(incoming)
|
let rawConfig = try encoder.encode(incoming)
|
||||||
|
@ -307,7 +307,7 @@ class Site: Codable {
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
|
|
||||||
MobileNebulaTestConfig(strConfig, key, &err)
|
MobileNebulaTestConfig(strConfig, key, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
throw err!
|
throw err!
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -322,12 +322,12 @@ class Site: Codable {
|
||||||
throw SiteError.keyLoad
|
throw SiteError.keyLoad
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: make sure this is valid on return!
|
// TODO: make sure this is valid on return!
|
||||||
return String(decoding: keyData, as: UTF8.self)
|
return String(decoding: keyData, as: UTF8.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDNCredentials() throws -> DNCredentials {
|
func getDNCredentials() throws -> DNCredentials {
|
||||||
if (!managed) {
|
if !managed {
|
||||||
throw SiteError.unmanagedGetCredentials
|
throw SiteError.unmanagedGetCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ class Site: Codable {
|
||||||
let creds = try getDNCredentials()
|
let creds = try getDNCredentials()
|
||||||
creds.invalid = true
|
creds.invalid = true
|
||||||
|
|
||||||
if (!(try creds.save(siteID: self.id))) {
|
if try !(creds.save(siteID: id)) {
|
||||||
throw SiteError.dnCredentialLoad
|
throw SiteError.dnCredentialLoad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,13 +353,13 @@ class Site: Codable {
|
||||||
let creds = try getDNCredentials()
|
let creds = try getDNCredentials()
|
||||||
creds.invalid = false
|
creds.invalid = false
|
||||||
|
|
||||||
if (!(try creds.save(siteID: self.id))) {
|
if try !(creds.save(siteID: id)) {
|
||||||
throw SiteError.dnCredentialSave
|
throw SiteError.dnCredentialSave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfig() throws -> Data {
|
func getConfig() throws -> Data {
|
||||||
return try self.incomingSite!.getConfig()
|
return try incomingSite!.getConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limits what we export to the UI
|
// Limits what we export to the UI
|
||||||
|
@ -429,7 +429,7 @@ class DNCredentials: Codable {
|
||||||
struct IncomingSite: Codable {
|
struct IncomingSite: Codable {
|
||||||
var name: String
|
var name: String
|
||||||
var id: String
|
var id: String
|
||||||
var staticHostmap: Dictionary<String, StaticHosts>
|
var staticHostmap: [String: StaticHosts]
|
||||||
var unsafeRoutes: [UnsafeRoute]?
|
var unsafeRoutes: [UnsafeRoute]?
|
||||||
var cert: String?
|
var cert: String?
|
||||||
var ca: String?
|
var ca: String?
|
||||||
|
@ -456,11 +456,11 @@ struct IncomingSite: Codable {
|
||||||
return try encoder.encode(config)
|
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
|
let configPath: URL
|
||||||
|
|
||||||
do {
|
do {
|
||||||
configPath = try SiteList.getSiteConfigFile(id: self.id, createDir: true)
|
configPath = try SiteList.getSiteConfigFile(id: id, createDir: true)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
callback(error)
|
callback(error)
|
||||||
|
@ -469,45 +469,44 @@ struct IncomingSite: Codable {
|
||||||
|
|
||||||
log.notice("Saving to \(configPath, privacy: .public)")
|
log.notice("Saving to \(configPath, privacy: .public)")
|
||||||
do {
|
do {
|
||||||
if (self.key != nil) {
|
if key != nil {
|
||||||
let data = self.key!.data(using: .utf8)
|
let data = key!.data(using: .utf8)
|
||||||
if (!KeyChain.save(key: "\(self.id).key", data: data!, managed: self.managed ?? false)) {
|
if !KeyChain.save(key: "\(id).key", data: data!, managed: managed ?? false) {
|
||||||
return callback(SiteError.keySave)
|
return callback(SiteError.keySave)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ((try self.dnCredentials?.save(siteID: self.id)) == false) {
|
if try (dnCredentials?.save(siteID: id)) == false {
|
||||||
return callback(SiteError.dnCredentialSave)
|
return callback(SiteError.dnCredentialSave)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.getConfig().write(to: configPath)
|
try getConfig().write(to: configPath)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
#if targetEnvironment(simulator)
|
// We are on a simulator and there is no NEVPNManager for us to interact with
|
||||||
// We are on a simulator and there is no NEVPNManager for us to interact with
|
|
||||||
callback(nil)
|
|
||||||
#else
|
|
||||||
if saveToManager {
|
|
||||||
self.saveToManager(manager: manager, callback: callback)
|
|
||||||
} else {
|
|
||||||
callback(nil)
|
callback(nil)
|
||||||
}
|
#else
|
||||||
#endif
|
if saveToManager {
|
||||||
|
self.saveToManager(manager: manager, callback: callback)
|
||||||
|
} else {
|
||||||
|
callback(nil)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveToManager(manager: NETunnelProviderManager?, callback: @escaping (Error?) -> ()) {
|
private func saveToManager(manager: NETunnelProviderManager?, callback: @escaping (Error?) -> Void) {
|
||||||
if (manager != nil) {
|
if manager != nil {
|
||||||
// We need to refresh our settings to properly update config
|
// We need to refresh our settings to properly update config
|
||||||
manager?.loadFromPreferences { error in
|
manager?.loadFromPreferences { error in
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,25 +518,25 @@ struct IncomingSite: Codable {
|
||||||
return finishSaveToManager(manager: NETunnelProviderManager(), callback: callback)
|
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
|
// Stuff our details in the protocol
|
||||||
let proto = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
|
let proto = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
|
||||||
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension";
|
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension"
|
||||||
// WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match
|
// WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match
|
||||||
// managers in PacketTunnelProvider.findManager
|
// managers in PacketTunnelProvider.findManager
|
||||||
proto.providerConfiguration = ["id": self.id]
|
proto.providerConfiguration = ["id": id]
|
||||||
proto.serverAddress = "Nebula"
|
proto.serverAddress = "Nebula"
|
||||||
|
|
||||||
// Finish up the manager, this is what stores everything at the system level
|
// Finish up the manager, this is what stores everything at the system level
|
||||||
manager.protocolConfiguration = proto
|
manager.protocolConfiguration = proto
|
||||||
//TODO: cert name? manager.protocolConfiguration?.username
|
// TODO: cert name? manager.protocolConfiguration?.username
|
||||||
|
|
||||||
//TODO: This is what is shown on the vpn page. We should add more identifying details in
|
// TODO: This is what is shown on the vpn page. We should add more identifying details in
|
||||||
manager.localizedDescription = self.name
|
manager.localizedDescription = name
|
||||||
manager.isEnabled = true
|
manager.isEnabled = true
|
||||||
|
|
||||||
manager.saveToPreferences{ error in
|
manager.saveToPreferences { error in
|
||||||
return callback(error)
|
callback(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ class SiteList {
|
||||||
let fileManager = FileManager.default
|
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)
|
try fileManager.createDirectory(at: rootDir, withIntermediateDirectories: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class SiteList {
|
||||||
static func getSitesDir() throws -> URL {
|
static func getSitesDir() throws -> URL {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let sitesDir = try getRootDir().appendingPathComponent("sites", isDirectory: true)
|
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)
|
try fileManager.createDirectory(at: sitesDir, withIntermediateDirectories: true)
|
||||||
}
|
}
|
||||||
return sitesDir
|
return sitesDir
|
||||||
|
@ -29,7 +29,7 @@ class SiteList {
|
||||||
static func getSiteDir(id: String, create: Bool = false) throws -> URL {
|
static func getSiteDir(id: String, create: Bool = false) throws -> URL {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let siteDir = try getSitesDir().appendingPathComponent(id, isDirectory: true)
|
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)
|
try fileManager.createDirectory(at: siteDir, withIntermediateDirectories: true)
|
||||||
}
|
}
|
||||||
return siteDir
|
return siteDir
|
||||||
|
@ -45,25 +45,25 @@ class SiteList {
|
||||||
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?) -> ()) {
|
init(completion: @escaping ([String: Site]?, Error?) -> Void) {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
SiteList.loadAllFromFS { sites, err in
|
SiteList.loadAllFromFS { sites, err in
|
||||||
if sites != nil {
|
if sites != nil {
|
||||||
self.sites = sites!
|
self.sites = sites!
|
||||||
|
}
|
||||||
|
completion(sites, err)
|
||||||
}
|
}
|
||||||
completion(sites, err)
|
#else
|
||||||
}
|
SiteList.loadAllFromNETPM { sites, err in
|
||||||
#else
|
if sites != nil {
|
||||||
SiteList.loadAllFromNETPM { sites, err in
|
self.sites = sites!
|
||||||
if sites != nil {
|
}
|
||||||
self.sites = sites!
|
completion(sites, err)
|
||||||
}
|
}
|
||||||
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
|
let fileManager = FileManager.default
|
||||||
var siteDirs: [URL]
|
var siteDirs: [URL]
|
||||||
var sites = [String: Site]()
|
var sites = [String: Site]()
|
||||||
|
@ -76,7 +76,7 @@ class SiteList {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
siteDirs.forEach { path in
|
for path in siteDirs {
|
||||||
do {
|
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
|
sites[site.id] = site
|
||||||
|
@ -91,15 +91,15 @@ class SiteList {
|
||||||
completion(sites, nil)
|
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]()
|
var sites = [String: Site]()
|
||||||
|
|
||||||
// dispatchGroup is used to ensure we have migrated all sites before returning them
|
// 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
|
// If there are no sites to migrate, there are never any entrants
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
NETunnelProviderManager.loadAllFromPreferences() { newManagers, err in
|
NETunnelProviderManager.loadAllFromPreferences { newManagers, err in
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return completion(nil, err)
|
return completion(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +121,10 @@ class SiteList {
|
||||||
sites[site.id] = site
|
sites[site.id] = site
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
//TODO: notify the user about this
|
// TODO: notify the user about this
|
||||||
print("Deleted non conforming site \(manager) \(error)")
|
print("Deleted non conforming site \(manager) \(error)")
|
||||||
manager.removeFromPreferences()
|
manager.removeFromPreferences()
|
||||||
//TODO: delete from disk, we need to try and discover the site id though
|
// TODO: delete from disk, we need to try and discover the site id though
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,17 +26,18 @@ class APIClient {
|
||||||
hostID: hostID,
|
hostID: hostID,
|
||||||
privateKey: privateKey,
|
privateKey: privateKey,
|
||||||
counter: counter,
|
counter: counter,
|
||||||
trustedKeys: trustedKeys)
|
trustedKeys: trustedKeys
|
||||||
|
)
|
||||||
} catch {
|
} catch {
|
||||||
// type information from Go is not available, use string matching instead
|
// 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 APIClientError.invalidCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.fetchedUpdate) {
|
if res.fetchedUpdate {
|
||||||
return try decodeIncomingSite(jsonSite: res.site)
|
return try decodeIncomingSite(jsonSite: res.site)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
import UIKit
|
||||||
|
|
||||||
enum ChannelName {
|
enum ChannelName {
|
||||||
static let vpn = "net.defined.mobileNebula/NebulaVpnService"
|
static let vpn = "net.defined.mobileNebula/NebulaVpnService"
|
||||||
|
@ -19,18 +19,16 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
private var sites: Sites?
|
private var sites: Sites?
|
||||||
private var ui: FlutterMethodChannel?
|
private var ui: FlutterMethodChannel?
|
||||||
|
|
||||||
|
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
|
||||||
|
|
||||||
dnUpdater.updateAllLoop { site in
|
dnUpdater.updateAllLoop { site in
|
||||||
// Signal the site has changed in case the current site details screen is active
|
// Signal the site has changed in case the current site details screen is active
|
||||||
let container = self.sites?.getContainer(id: site.id)
|
let container = self.sites?.getContainer(id: site.id)
|
||||||
if (container != nil) {
|
if container != nil {
|
||||||
// Update references to the site with the new site config
|
// Update references to the site with the new site config
|
||||||
container!.site = site
|
container!.site = site
|
||||||
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
|
container!.updater.update(connected: site.connected ?? false, replaceSite: site)
|
||||||
|
@ -47,42 +45,38 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
sites = Sites(messenger: controller.binaryMessenger)
|
sites = Sites(messenger: controller.binaryMessenger)
|
||||||
ui = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: 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) in
|
||||||
switch call.method {
|
switch call.method {
|
||||||
case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result)
|
case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result)
|
||||||
case "nebula.generateKeyPair": return self.nebulaGenerateKeyPair(result: result)
|
case "nebula.generateKeyPair": return self.nebulaGenerateKeyPair(result: result)
|
||||||
case "nebula.renderConfig": return self.nebulaRenderConfig(call: call, result: result)
|
case "nebula.renderConfig": return self.nebulaRenderConfig(call: call, result: result)
|
||||||
case "nebula.verifyCertAndKey": return self.nebulaVerifyCertAndKey(call: call, result: result)
|
case "nebula.verifyCertAndKey": return self.nebulaVerifyCertAndKey(call: call, result: result)
|
||||||
|
|
||||||
case "dn.enroll": return self.dnEnroll(call: call, result: result)
|
case "dn.enroll": return self.dnEnroll(call: call, result: result)
|
||||||
|
|
||||||
case "listSites": return self.listSites(result: result)
|
case "listSites": return self.listSites(result: result)
|
||||||
case "deleteSite": return self.deleteSite(call: call, result: result)
|
case "deleteSite": return self.deleteSite(call: call, result: result)
|
||||||
case "saveSite": return self.saveSite(call: call, result: result)
|
case "saveSite": return self.saveSite(call: call, result: result)
|
||||||
case "startSite": return self.startSite(call: call, result: result)
|
case "startSite": return self.startSite(call: call, result: result)
|
||||||
case "stopSite": return self.stopSite(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.listHostmap": self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
|
||||||
case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", 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.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
|
||||||
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", 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.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
result(FlutterMethodNotImplemented)
|
result(FlutterMethodNotImplemented)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func nebulaParseCerts(call: FlutterMethodCall, result: FlutterResult) {
|
func nebulaParseCerts(call: FlutterMethodCall, result: FlutterResult) {
|
||||||
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
|
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")) }
|
guard let certs = args["certs"] else { return result(MissingArgumentError(message: "certs is a required argument")) }
|
||||||
|
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
let json = MobileNebulaParseCerts(certs, &err)
|
let json = MobileNebulaParseCerts(certs, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return result(CallFailedError(message: "Error while parsing certificate(s)", details: err!.localizedDescription))
|
return result(CallFailedError(message: "Error while parsing certificate(s)", details: err!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +84,14 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
}
|
}
|
||||||
|
|
||||||
func nebulaVerifyCertAndKey(call: FlutterMethodCall, result: FlutterResult) {
|
func nebulaVerifyCertAndKey(call: FlutterMethodCall, result: FlutterResult) {
|
||||||
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
|
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 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 key = args["key"] else { return result(MissingArgumentError(message: "key is a required argument")) }
|
||||||
|
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
var validd: ObjCBool = false
|
var validd: ObjCBool = false
|
||||||
let valid = MobileNebulaVerifyCertAndKey(cert, key, &validd, &err)
|
let valid = MobileNebulaVerifyCertAndKey(cert, key, &validd, &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return result(CallFailedError(message: "Error while verifying certificate and private key", details: err!.localizedDescription))
|
return result(CallFailedError(message: "Error while verifying certificate and private key", details: err!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +101,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
func nebulaGenerateKeyPair(result: FlutterResult) {
|
func nebulaGenerateKeyPair(result: FlutterResult) {
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
let kp = MobileNebulaGenerateKeyPair(&err)
|
let kp = MobileNebulaGenerateKeyPair(&err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return result(CallFailedError(message: "Error while generating key pairs", details: err!.localizedDescription))
|
return result(CallFailedError(message: "Error while generating key pairs", details: err!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +113,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
|
|
||||||
var err: NSError?
|
var err: NSError?
|
||||||
let yaml = MobileNebulaRenderConfig(config, "<hidden>", &err)
|
let yaml = MobileNebulaRenderConfig(config, "<hidden>", &err)
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return result(CallFailedError(message: "Error while rendering config", details: err!.localizedDescription))
|
return result(CallFailedError(message: "Error while rendering config", details: err!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,9 +126,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
do {
|
do {
|
||||||
let site = try apiClient.enroll(code: code)
|
let site = try apiClient.enroll(code: code)
|
||||||
|
|
||||||
let oldSite = self.sites?.getSite(id: site.id)
|
let oldSite = sites?.getSite(id: site.id)
|
||||||
site.save(manager: oldSite?.manager) { error in
|
site.save(manager: oldSite?.manager) { error in
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
return result(CallFailedError(message: "Failed to enroll", details: error!.localizedDescription))
|
return result(CallFailedError(message: "Failed to enroll", details: error!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,8 +140,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
}
|
}
|
||||||
|
|
||||||
func listSites(result: @escaping FlutterResult) {
|
func listSites(result: @escaping FlutterResult) {
|
||||||
self.sites?.loadSites { (sites, err) -> () in
|
sites?.loadSites { sites, err in
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return result(CallFailedError(message: "Failed to load site list", details: err!.localizedDescription))
|
return result(CallFailedError(message: "Failed to load site list", details: err!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,9 +154,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
|
|
||||||
func deleteSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
func deleteSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
guard let id = call.arguments as? String else { return result(NoArgumentsError()) }
|
guard let id = call.arguments as? String else { return result(NoArgumentsError()) }
|
||||||
//TODO: stop the site if its running currently
|
// TODO: stop the site if its running currently
|
||||||
self.sites?.deleteSite(id: id) { error in
|
sites?.deleteSite(id: id) { error in
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
result(CallFailedError(message: "Failed to delete site", details: error!.localizedDescription))
|
result(CallFailedError(message: "Failed to delete site", details: error!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,9 +172,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
return result(NoArgumentsError())
|
return result(NoArgumentsError())
|
||||||
}
|
}
|
||||||
|
|
||||||
let oldSite = self.sites?.getSite(id: site.id)
|
let oldSite = sites?.getSite(id: site.id)
|
||||||
site.save(manager: oldSite?.manager) { error in
|
site.save(manager: oldSite?.manager) { error in
|
||||||
if (error != nil) {
|
if error != nil {
|
||||||
return result(CallFailedError(message: "Failed to save site", details: error!.localizedDescription))
|
return result(CallFailedError(message: "Failed to save site", details: error!.localizedDescription))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,58 +185,58 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
|
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")) }
|
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)
|
let updater = sites?.getUpdater(id: id)
|
||||||
updater?.update(connected: true)
|
updater?.update(connected: true)
|
||||||
#else
|
#else
|
||||||
let container = self.sites?.getContainer(id: id)
|
let container = sites?.getContainer(id: id)
|
||||||
let manager = container?.site.manager
|
let manager = container?.site.manager
|
||||||
|
|
||||||
manager?.loadFromPreferences{ error in
|
manager?.loadFromPreferences { error in
|
||||||
//TODO: Handle load error
|
// TODO: Handle load error
|
||||||
// This is silly but we need to enable the site each time to avoid situations where folks have multiple sites
|
// This is silly but we need to enable the site each time to avoid situations where folks have multiple sites
|
||||||
manager?.isEnabled = true
|
manager?.isEnabled = true
|
||||||
manager?.saveToPreferences{ error in
|
manager?.saveToPreferences { error in
|
||||||
//TODO: Handle load error
|
// TODO: Handle load error
|
||||||
manager?.loadFromPreferences{ error in
|
manager?.loadFromPreferences { error in
|
||||||
//TODO: Handle load error
|
// TODO: Handle load error
|
||||||
do {
|
do {
|
||||||
container?.updater.startFunc = {() -> Void in
|
container?.updater.startFunc = { () in
|
||||||
return self.vpnRequest(command: "start", arguments: args, result: result)
|
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))
|
||||||
}
|
}
|
||||||
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
|
|
||||||
} catch {
|
|
||||||
return result(CallFailedError(message: "Could not start site", details: error.localizedDescription))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
func stopSite(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
guard let args = call.arguments as? Dictionary<String, String> else { return result(NoArgumentsError()) }
|
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")) }
|
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)
|
let updater = sites?.getUpdater(id: id)
|
||||||
updater?.update(connected: false)
|
updater?.update(connected: false)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
let manager = self.sites?.getSite(id: id)?.manager
|
let manager = sites?.getSite(id: id)?.manager
|
||||||
manager?.loadFromPreferences{ error in
|
manager?.loadFromPreferences { _ in
|
||||||
//TODO: Handle load error
|
// TODO: Handle load error
|
||||||
|
|
||||||
manager?.connection.stopVPNTunnel()
|
manager?.connection.stopVPNTunnel()
|
||||||
return result(nil)
|
return result(nil)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
|
func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
|
||||||
guard let args = arguments as? Dictionary<String, Any> else { return result(NoArgumentsError()) }
|
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")) }
|
guard let id = args["id"] as? String else { return result(MissingArgumentError(message: "id is a required argument")) }
|
||||||
let container = sites?.getContainer(id: id)
|
let container = sites?.getContainer(id: id)
|
||||||
|
|
||||||
|
@ -258,12 +252,12 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
|
|
||||||
if let session = container!.site.manager?.connection as? NETunnelProviderSession {
|
if let session = container!.site.manager?.connection as? NETunnelProviderSession {
|
||||||
do {
|
do {
|
||||||
try session.sendProviderMessage(try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in
|
try session.sendProviderMessage(JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return result(nil)
|
return result(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//print(String(decoding: data!, as: UTF8.self))
|
// print(String(decoding: data!, as: UTF8.self))
|
||||||
guard let res = try? JSONDecoder().decode(IPCResponse.self, from: data!) else {
|
guard let res = try? JSONDecoder().decode(IPCResponse.self, from: data!) else {
|
||||||
return result(CallFailedError(message: "Failed to decode response"))
|
return result(CallFailedError(message: "Failed to decode response"))
|
||||||
}
|
}
|
||||||
|
@ -278,7 +272,7 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
return result(CallFailedError(message: error.localizedDescription))
|
return result(CallFailedError(message: error.localizedDescription))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen?
|
// TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen?
|
||||||
result(nil)
|
result(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ class DNUpdater {
|
||||||
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
|
private let timer = RepeatingTimer(timeInterval: 15 * 60) // 15 * 60 is 15 minutes
|
||||||
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
|
private let log = Logger(subsystem: "net.defined.mobileNebula", category: "DNUpdater")
|
||||||
|
|
||||||
func updateAll(onUpdate: @escaping (Site) -> ()) {
|
func updateAll(onUpdate: @escaping (Site) -> Void) {
|
||||||
_ = SiteList{ (sites, _) -> () in
|
_ = SiteList { sites, _ in
|
||||||
// NEVPN seems to force us onto the main thread and we are about to make network calls that
|
// 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.
|
// could block for a while. Push ourselves onto another thread to avoid blocking the UI.
|
||||||
Task.detached(priority: .userInitiated) {
|
Task.detached(priority: .userInitiated) {
|
||||||
sites?.values.forEach { site in
|
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
|
// The vpn service is in charge of updating the currently connected site
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -23,23 +23,23 @@ class DNUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAllLoop(onUpdate: @escaping (Site) -> ()) {
|
func updateAllLoop(onUpdate: @escaping (Site) -> Void) {
|
||||||
timer.eventHandler = {
|
timer.eventHandler = {
|
||||||
self.updateAll(onUpdate: onUpdate)
|
self.updateAll(onUpdate: onUpdate)
|
||||||
}
|
}
|
||||||
timer.resume()
|
timer.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> ()) {
|
func updateSingleLoop(site: Site, onUpdate: @escaping (Site) -> Void) {
|
||||||
timer.eventHandler = {
|
timer.eventHandler = {
|
||||||
self.updateSite(site: site, onUpdate: onUpdate)
|
self.updateSite(site: site, onUpdate: onUpdate)
|
||||||
}
|
}
|
||||||
timer.resume()
|
timer.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSite(site: Site, onUpdate: @escaping (Site) -> ()) {
|
func updateSite(site: Site, onUpdate: @escaping (Site) -> Void) {
|
||||||
do {
|
do {
|
||||||
if (!site.managed) {
|
if !site.managed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class DNUpdater {
|
||||||
trustedKeys: credentials.trustedKeys
|
trustedKeys: credentials.trustedKeys
|
||||||
)
|
)
|
||||||
} catch (APIClientError.invalidCredentials) {
|
} catch (APIClientError.invalidCredentials) {
|
||||||
if (!credentials.invalid) {
|
if !credentials.invalid {
|
||||||
try site.invalidateDNCredentials()
|
try site.invalidateDNCredentials()
|
||||||
log.notice("Invalidated credentials in site: \(site.name, privacy: .public)")
|
log.notice("Invalidated credentials in site: \(site.name, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ class DNUpdater {
|
||||||
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
|
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)")
|
self.log.error("failed to save update: \(error!.localizedDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class DNUpdater {
|
||||||
onUpdate(Site(incoming: newSite!))
|
onUpdate(Site(incoming: newSite!))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentials.invalid) {
|
if credentials.invalid {
|
||||||
try site.validateDNCredentials()
|
try site.validateDNCredentials()
|
||||||
log.notice("Revalidated credentials in site \(site.name, privacy: .public)")
|
log.notice("Revalidated credentials in site \(site.name, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,6 @@ class DNUpdater {
|
||||||
|
|
||||||
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
|
// From https://medium.com/over-engineering/a-background-repeating-timer-in-swift-412cecfd2ef9
|
||||||
class RepeatingTimer {
|
class RepeatingTimer {
|
||||||
|
|
||||||
let timeInterval: TimeInterval
|
let timeInterval: TimeInterval
|
||||||
|
|
||||||
init(timeInterval: TimeInterval) {
|
init(timeInterval: TimeInterval) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ class PackageInfo {
|
||||||
"unknown"
|
"unknown"
|
||||||
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
|
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
|
||||||
|
|
||||||
if (buildNumber == nil) {
|
if buildNumber == nil {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
class SiteContainer {
|
class SiteContainer {
|
||||||
var site: Site
|
var site: Site
|
||||||
|
@ -19,15 +19,15 @@ class Sites {
|
||||||
self.messenger = messenger
|
self.messenger = messenger
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSites(completion: @escaping ([String: Site]?, Error?) -> ()) {
|
func loadSites(completion: @escaping ([String: Site]?, Error?) -> Void) {
|
||||||
_ = SiteList { (sites, err) in
|
_ = SiteList { sites, err in
|
||||||
if (err != nil) {
|
if err != nil {
|
||||||
return completion(nil, err)
|
return completion(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sites?.values.forEach{ site in
|
sites?.values.forEach { site in
|
||||||
var updater = self.containers[site.id]?.updater
|
var updater = self.containers[site.id]?.updater
|
||||||
if (updater != nil) {
|
if updater != nil {
|
||||||
updater!.setSite(site: site)
|
updater!.setSite(site: site)
|
||||||
} else {
|
} else {
|
||||||
updater = SiteUpdater(messenger: self.messenger!, site: site)
|
updater = SiteUpdater(messenger: self.messenger!, site: site)
|
||||||
|
@ -36,14 +36,14 @@ class Sites {
|
||||||
}
|
}
|
||||||
|
|
||||||
let justSites = self.containers.mapValues {
|
let justSites = self.containers.mapValues {
|
||||||
return $0.site
|
$0.site
|
||||||
}
|
}
|
||||||
completion(justSites, nil)
|
completion(justSites, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteSite(id: String, callback: @escaping (Error?) -> ()) {
|
func deleteSite(id: String, callback: @escaping (Error?) -> Void) {
|
||||||
if let site = self.containers.removeValue(forKey: id) {
|
if let site = containers.removeValue(forKey: id) {
|
||||||
_ = KeyChain.delete(key: "\(site.site.id).dnCredentials")
|
_ = KeyChain.delete(key: "\(site.site.id).dnCredentials")
|
||||||
_ = KeyChain.delete(key: "\(site.site.id).key")
|
_ = KeyChain.delete(key: "\(site.site.id).key")
|
||||||
|
|
||||||
|
@ -55,10 +55,10 @@ class Sites {
|
||||||
print("Failed to delete site from fs: \(error.localizedDescription)")
|
print("Failed to delete site from fs: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
site.site.manager!.removeFromPreferences(completionHandler: callback)
|
site.site.manager!.removeFromPreferences(completionHandler: callback)
|
||||||
return
|
return
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing to remove
|
// Nothing to remove
|
||||||
|
@ -66,21 +66,21 @@ class Sites {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSite(id: String) -> Site? {
|
func getSite(id: String) -> Site? {
|
||||||
return self.containers[id]?.site
|
return containers[id]?.site
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUpdater(id: String) -> SiteUpdater? {
|
func getUpdater(id: String) -> SiteUpdater? {
|
||||||
return self.containers[id]?.updater
|
return containers[id]?.updater
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainer(id: String) -> SiteContainer? {
|
func getContainer(id: String) -> SiteContainer? {
|
||||||
return self.containers[id]
|
return containers[id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SiteUpdater: NSObject, FlutterStreamHandler {
|
class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
private var eventSink: FlutterEventSink?;
|
private var eventSink: FlutterEventSink?
|
||||||
private var eventChannel: FlutterEventChannel;
|
private var eventChannel: FlutterEventChannel
|
||||||
private var site: Site
|
private var site: Site
|
||||||
private var notification: Any?
|
private var notification: Any?
|
||||||
public var startFunc: (() -> Void)?
|
public var startFunc: (() -> Void)?
|
||||||
|
@ -90,15 +90,15 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
init(messenger: FlutterBinaryMessenger, site: Site) {
|
init(messenger: FlutterBinaryMessenger, site: Site) {
|
||||||
do {
|
do {
|
||||||
let configPath = try SiteList.getSiteConfigFile(id: site.id, createDir: false)
|
let configPath = try SiteList.getSiteConfigFile(id: site.id, createDir: false)
|
||||||
self.configFd = open(configPath.path, O_EVTONLY)
|
configFd = open(configPath.path, O_EVTONLY)
|
||||||
self.configObserver = DispatchSource.makeFileSystemObjectSource(
|
configObserver = DispatchSource.makeFileSystemObjectSource(
|
||||||
fileDescriptor: self.configFd!,
|
fileDescriptor: configFd!,
|
||||||
eventMask: .write
|
eventMask: .write
|
||||||
)
|
)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
// SiteList.getSiteConfigFile should never throw because we are not creating it here
|
// SiteList.getSiteConfigFile should never throw because we are not creating it here
|
||||||
self.configObserver = nil
|
configObserver = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
|
eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger)
|
||||||
|
@ -107,15 +107,15 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
|
|
||||||
eventChannel.setStreamHandler(self)
|
eventChannel.setStreamHandler(self)
|
||||||
|
|
||||||
self.configObserver?.setEventHandler(handler: self.configUpdated)
|
configObserver?.setEventHandler(handler: configUpdated)
|
||||||
self.configObserver?.setCancelHandler {
|
configObserver?.setCancelHandler {
|
||||||
if self.configFd != nil {
|
if self.configFd != nil {
|
||||||
close(self.configFd!)
|
close(self.configFd!)
|
||||||
}
|
}
|
||||||
self.configObserver = nil
|
self.configObserver = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
self.configObserver?.resume()
|
configObserver?.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSite(site: Site) {
|
func setSite(site: Site) {
|
||||||
|
@ -123,44 +123,44 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// onListen is called when flutter code attaches an event listener
|
/// onListen is called when flutter code attaches an event listener
|
||||||
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
func onListen(withArguments _: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||||
eventSink = events;
|
eventSink = events
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
if site.manager == nil {
|
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
|
// 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.
|
// 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
|
|
||||||
let oldConnected = self.site.connected
|
|
||||||
self.site.status = statusString[self.site.manager!.connection.status]
|
|
||||||
self.site.connected = statusMap[self.site.manager!.connection.status]
|
|
||||||
|
|
||||||
// Check to see if we just moved to connected and if we have a start function to call when that happens
|
|
||||||
if self.site.connected! && oldConnected != self.site.connected && self.startFunc != nil {
|
|
||||||
self.startFunc!()
|
|
||||||
self.startFunc = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update(connected: self.site.connected!)
|
notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection, queue: nil) { _ in
|
||||||
}
|
let oldConnected = self.site.connected
|
||||||
#endif
|
self.site.status = statusString[self.site.manager!.connection.status]
|
||||||
|
self.site.connected = statusMap[self.site.manager!.connection.status]
|
||||||
|
|
||||||
|
// Check to see if we just moved to connected and if we have a start function to call when that happens
|
||||||
|
if self.site.connected!, oldConnected != self.site.connected, self.startFunc != nil {
|
||||||
|
self.startFunc!()
|
||||||
|
self.startFunc = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update(connected: self.site.connected!)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/// onCancel is called when the flutter listener stops listening
|
/// onCancel is called when the flutter listener stops listening
|
||||||
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
func onCancel(withArguments _: Any?) -> FlutterError? {
|
||||||
if (self.notification != nil) {
|
if notification != nil {
|
||||||
NotificationCenter.default.removeObserver(self.notification!)
|
NotificationCenter.default.removeObserver(notification!)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update is a way to send information to the flutter listener and generally should not be used directly
|
/// 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) {
|
func update(connected: Bool, replaceSite: Site? = nil) {
|
||||||
if (replaceSite != nil) {
|
if replaceSite != nil {
|
||||||
site = replaceSite!
|
site = replaceSite!
|
||||||
}
|
}
|
||||||
site.connected = connected
|
site.connected = connected
|
||||||
|
@ -168,18 +168,18 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
let data = try! encoder.encode(site)
|
let data = try! encoder.encode(site)
|
||||||
self.eventSink?(String(data: data, encoding: .utf8))
|
eventSink?(String(data: data, encoding: .utf8))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configUpdated() {
|
private func configUpdated() {
|
||||||
if self.site.connected != true {
|
if site.connected != true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let newSite = try? Site(manager: self.site.manager!) else {
|
guard let newSite = try? Site(manager: site.manager!) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update(connected: newSite.connected ?? false, replaceSite: newSite)
|
update(connected: newSite.connected ?? false, replaceSite: newSite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue