mirror of
https://github.com/DefinedNet/mobile_nebula.git
synced 2025-02-23 19:45:26 +00:00
Format swift code
This commit is contained in:
parent
f1306d82d4
commit
e87b7809b4
9 changed files with 1569 additions and 1491 deletions
|
@ -5,13 +5,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ class KeyChain {
|
||||||
|
|
||||||
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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@ 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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -56,7 +56,9 @@ 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>) -> Void in
|
||||||
data = Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
|
data = Data(buffer: UnsafeBufferPointer(start: ptr, count: 1))
|
||||||
})
|
})
|
||||||
self.init(data)
|
self.init(data)
|
||||||
|
@ -66,4 +68,3 @@ extension Data {
|
||||||
return self.withUnsafeBytes { $0.load(as: T.self) }
|
return self.withUnsafeBytes { $0.load(as: T.self) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
import os.log
|
import NetworkExtension
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
import os.log
|
||||||
|
|
||||||
enum VPNStartError: Error {
|
enum VPNStartError: Error {
|
||||||
case noManagers
|
case noManagers
|
||||||
|
@ -23,7 +23,6 @@ extension AppMessageError: LocalizedError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -55,7 +54,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
do {
|
do {
|
||||||
// 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 self.findManager()
|
||||||
guard let foundManager = manager else {
|
guard let foundManager = manager else {
|
||||||
throw VPNStartError.couldNotFindManager
|
throw VPNStartError.couldNotFindManager
|
||||||
|
@ -87,16 +88,19 @@ 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(
|
||||||
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
|
addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
||||||
|
var routes: [NEIPv4Route] = [
|
||||||
|
NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)
|
||||||
|
]
|
||||||
|
|
||||||
// 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))
|
||||||
|
@ -107,7 +111,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
||||||
var nebulaErr: NSError?
|
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()
|
self.startNetworkMonitor()
|
||||||
|
|
||||||
if nebulaErr != nil {
|
if nebulaErr != nil {
|
||||||
|
@ -122,18 +127,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private func handleDNUpdate(newSite: Site) {
|
private func handleDNUpdate(newSite: Site) {
|
||||||
do {
|
do {
|
||||||
self.site = newSite
|
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 {
|
} 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 = self.protocolConfiguration as? NETunnelProviderProtocol
|
||||||
|
@ -150,7 +157,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +177,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
networkMonitor = nil
|
networkMonitor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(
|
||||||
|
with reason: NEProviderStopReason, completionHandler: @escaping () -> Void
|
||||||
|
) {
|
||||||
nebula?.stop()
|
nebula?.stop()
|
||||||
stopNetworkMonitor()
|
stopNetworkMonitor()
|
||||||
completionHandler()
|
completionHandler()
|
||||||
|
@ -181,7 +190,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
if routeDescription != cachedRouteDescription {
|
if routeDescription != cachedRouteDescription {
|
||||||
// Don't bother to rebind if we don't have any gateways
|
// Don't bother to rebind if we don't have any gateways
|
||||||
if routeDescription != "" {
|
if routeDescription != "" {
|
||||||
nebula?.rebind("network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")")
|
nebula?.rebind(
|
||||||
|
"network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")")
|
||||||
}
|
}
|
||||||
cachedRouteDescription = routeDescription
|
cachedRouteDescription = routeDescription
|
||||||
}
|
}
|
||||||
|
@ -189,7 +199,7 @@ 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
|
endpoints.forEach { endpoint in
|
||||||
switch endpoint {
|
switch endpoint {
|
||||||
case let .hostPort(.ipv6(host), port):
|
case let .hostPort(.ipv6(host), port):
|
||||||
str.append("[\(host)]:\(port)")
|
str.append("[\(host)]:\(port)")
|
||||||
|
@ -222,7 +232,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
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.init(type: .error, message: JSON(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,8 +255,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
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.init(
|
||||||
|
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.init(type: .success, message: data))
|
||||||
}
|
}
|
||||||
|
@ -259,13 +272,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private func getHostInfo(args: JSON) -> (JSON?, Error?) {
|
private func getHostInfo(args: JSON) -> (JSON?, Error?) {
|
||||||
var err: NSError?
|
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)
|
return (JSON(res), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) {
|
private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) {
|
||||||
var err: NSError?
|
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)
|
return (JSON(res), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,4 +321,3 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import NetworkExtension
|
|
||||||
import MobileNebula
|
import MobileNebula
|
||||||
|
import NetworkExtension
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
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
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
ca.forEach { cert in
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,11 +294,11 @@ class Site: Codable {
|
||||||
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 {
|
||||||
|
@ -327,7 +327,7 @@ class Site: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
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: self.id)) {
|
||||||
throw SiteError.dnCredentialLoad
|
throw SiteError.dnCredentialLoad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@ 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: self.id)) {
|
||||||
throw SiteError.dnCredentialSave
|
throw SiteError.dnCredentialSave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,7 +456,10 @@ 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 {
|
||||||
|
@ -469,15 +472,15 @@ struct IncomingSite: Codable {
|
||||||
|
|
||||||
log.notice("Saving to \(configPath, privacy: .public)")
|
log.notice("Saving to \(configPath, privacy: .public)")
|
||||||
do {
|
do {
|
||||||
if (self.key != nil) {
|
if self.key != nil {
|
||||||
let data = self.key!.data(using: .utf8)
|
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)
|
return callback(SiteError.keySave)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if ((try self.dnCredentials?.save(siteID: self.id)) == false) {
|
if (try self.dnCredentials?.save(siteID: self.id)) == false {
|
||||||
return callback(SiteError.dnCredentialSave)
|
return callback(SiteError.dnCredentialSave)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -490,24 +493,25 @@ struct IncomingSite: Codable {
|
||||||
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)
|
callback(nil)
|
||||||
#else
|
#else
|
||||||
if saveToManager {
|
if saveToManager {
|
||||||
self.saveToManager(manager: manager, callback: callback)
|
self.saveToManager(manager: manager, callback: callback)
|
||||||
} else {
|
} else {
|
||||||
callback(nil)
|
callback(nil)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveToManager(manager: NETunnelProviderManager?, callback: @escaping (Error?) -> ()) {
|
private func saveToManager(
|
||||||
if (manager != nil) {
|
manager: NETunnelProviderManager?, callback: @escaping (Error?) -> Void
|
||||||
|
) {
|
||||||
|
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,10 +523,13 @@ 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 =
|
||||||
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension";
|
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
|
// 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": self.id]
|
||||||
|
@ -536,7 +543,7 @@ struct IncomingSite: Codable {
|
||||||
manager.localizedDescription = self.name
|
manager.localizedDescription = self.name
|
||||||
manager.isEnabled = true
|
manager.isEnabled = true
|
||||||
|
|
||||||
manager.saveToPreferences{ error in
|
manager.saveToPreferences { error in
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
/// 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 {
|
static func getRootDir() throws -> URL {
|
||||||
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 +20,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 +30,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
|
||||||
|
@ -37,39 +38,43 @@ class SiteList {
|
||||||
|
|
||||||
/// Gets the file that represents the site configuration, $rootDir/sites/$siteID/config.json
|
/// Gets the file that represents the site configuration, $rootDir/sites/$siteID/config.json
|
||||||
static func getSiteConfigFile(id: String, createDir: Bool) throws -> URL {
|
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
|
/// Gets the file that represents the site log output, $rootDir/sites/$siteID/log
|
||||||
static func getSiteLogFile(id: String, createDir: Bool) throws -> URL {
|
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?) -> ()) {
|
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
|
#else
|
||||||
SiteList.loadAllFromNETPM { sites, err in
|
SiteList.loadAllFromNETPM { sites, err in
|
||||||
if sites != nil {
|
if sites != nil {
|
||||||
self.sites = sites!
|
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]()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
siteDirs = try fileManager.contentsOfDirectory(at: getSitesDir(), includingPropertiesForKeys: nil)
|
siteDirs = try fileManager.contentsOfDirectory(
|
||||||
|
at: getSitesDir(), includingPropertiesForKeys: nil)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
completion(nil, error)
|
completion(nil, error)
|
||||||
|
@ -78,7 +83,8 @@ class SiteList {
|
||||||
|
|
||||||
siteDirs.forEach { path in
|
siteDirs.forEach { path in
|
||||||
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
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -91,15 +97,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ class APIClient {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let packageInfo = PackageInfo()
|
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 {
|
func enroll(code: String) throws -> IncomingSite {
|
||||||
|
@ -18,7 +19,9 @@ class APIClient {
|
||||||
return try decodeIncomingSite(jsonSite: res.site)
|
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
|
let res: MobileNebulaTryUpdateResult
|
||||||
do {
|
do {
|
||||||
res = try apiClient.tryUpdate(
|
res = try apiClient.tryUpdate(
|
||||||
|
@ -29,14 +32,14 @@ class APIClient {
|
||||||
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,7 +45,7 @@ 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) -> Void 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)
|
||||||
|
@ -62,11 +60,16 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
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":
|
||||||
case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
|
self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result)
|
||||||
case "active.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result)
|
case "active.listPendingHostmap":
|
||||||
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
|
self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result)
|
||||||
case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", 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:
|
default:
|
||||||
result(FlutterMethodNotImplemented)
|
result(FlutterMethodNotImplemented)
|
||||||
|
@ -77,28 +80,39 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result(json)
|
return result(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
guard let key = args["key"] else { return result(MissingArgumentError(message: "key is a required argument")) }
|
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 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result(valid)
|
return result(valid)
|
||||||
|
@ -107,8 +121,10 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result(kp)
|
return result(kp)
|
||||||
|
@ -119,8 +135,10 @@ 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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result(yaml)
|
return result(yaml)
|
||||||
|
@ -134,21 +152,24 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
|
|
||||||
let oldSite = self.sites?.getSite(id: site.id)
|
let oldSite = self.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))
|
||||||
}
|
}
|
||||||
|
|
||||||
result(nil)
|
result(nil)
|
||||||
}
|
}
|
||||||
} catch {
|
} 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) {
|
func listSites(result: @escaping FlutterResult) {
|
||||||
self.sites?.loadSites { (sites, err) -> () in
|
self.sites?.loadSites { (sites, err) -> Void 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
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()) }
|
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
|
self.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))
|
||||||
}
|
}
|
||||||
|
|
||||||
result(nil)
|
result(nil)
|
||||||
|
@ -180,8 +202,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
|
|
||||||
let oldSite = self.sites?.getSite(id: site.id)
|
let oldSite = self.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))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sites?.loadSites { _, _ in
|
self.sites?.loadSites { _, _ in
|
||||||
|
@ -191,59 +214,67 @@ 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 = self.sites?.getUpdater(id: id)
|
||||||
updater?.update(connected: true)
|
updater?.update(connected: true)
|
||||||
#else
|
#else
|
||||||
let container = self.sites?.getContainer(id: id)
|
let container = self.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 = { () -> Void in
|
||||||
return self.vpnRequest(command: "start", arguments: args, result: result)
|
return self.vpnRequest(command: "start", arguments: args, result: result)
|
||||||
}
|
}
|
||||||
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
|
try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)])
|
||||||
} catch {
|
} 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) {
|
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 {
|
||||||
#if targetEnvironment(simulator)
|
return result(MissingArgumentError(message: "id is a required argument"))
|
||||||
|
}
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
let updater = self.sites?.getUpdater(id: id)
|
let updater = self.sites?.getUpdater(id: id)
|
||||||
updater?.update(connected: false)
|
updater?.update(connected: false)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
let manager = self.sites?.getSite(id: id)?.manager
|
let manager = self.sites?.getSite(id: id)?.manager
|
||||||
manager?.loadFromPreferences{ error in
|
manager?.loadFromPreferences { error 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)
|
||||||
|
|
||||||
if container == nil {
|
if container == nil {
|
||||||
|
@ -258,7 +289,9 @@ 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(
|
||||||
|
try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))
|
||||||
|
) { data in
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return result(nil)
|
return result(nil)
|
||||||
}
|
}
|
||||||
|
@ -272,7 +305,8 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
return result(res.message?.object)
|
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 {
|
} catch {
|
||||||
return result(CallFailedError(message: error.localizedDescription))
|
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)
|
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)
|
return FlutterError(code: "noArguments", message: message, details: details)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, _) -> Void 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)")
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,13 @@ class DNUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
let siteManager = site.manager
|
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
|
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,13 +78,15 @@ 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)")
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} 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)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,10 @@ import Foundation
|
||||||
|
|
||||||
class PackageInfo {
|
class PackageInfo {
|
||||||
func getVersion() -> String {
|
func getVersion() -> String {
|
||||||
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ??
|
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +13,8 @@ class PackageInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getName() -> String {
|
func getName() -> String {
|
||||||
return Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ??
|
return Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? Bundle.main
|
||||||
Bundle.main.infoDictionary?["CFBundleName"] as? String ??
|
.infoDictionary?["CFBundleName"] as? String ?? "Nebula"
|
||||||
"Nebula"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSystemVersion() -> String {
|
func getSystemVersion() -> String {
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -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) {
|
if let site = self.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
|
||||||
|
@ -79,8 +79,8 @@ class Sites {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)?
|
||||||
|
@ -101,7 +101,8 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
|
||||||
self.configObserver = nil
|
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
|
self.site = site
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -123,17 +124,23 @@ 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 arguments: Any?, eventSink events: @escaping FlutterEventSink)
|
||||||
eventSink = events;
|
-> FlutterError?
|
||||||
|
{
|
||||||
|
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
|
self.notification = NotificationCenter.default.addObserver(
|
||||||
|
forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection,
|
||||||
|
queue: nil
|
||||||
|
) { n in
|
||||||
let oldConnected = self.site.connected
|
let oldConnected = self.site.connected
|
||||||
self.site.status = statusString[self.site.manager!.connection.status]
|
self.site.status = statusString[self.site.manager!.connection.status]
|
||||||
self.site.connected = statusMap[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!)
|
self.update(connected: self.site.connected!)
|
||||||
}
|
}
|
||||||
#endif
|
#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 arguments: Any?) -> FlutterError? {
|
||||||
if (self.notification != nil) {
|
if self.notification != nil {
|
||||||
NotificationCenter.default.removeObserver(self.notification!)
|
NotificationCenter.default.removeObserver(self.notification!)
|
||||||
}
|
}
|
||||||
return nil
|
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
|
/// 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
|
||||||
|
|
Loading…
Reference in a new issue