]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
Updated NETunnelProvider save format
authorEric Kuck <eric@bluelinelabs.com>
Fri, 21 Dec 2018 04:52:45 +0000 (22:52 -0600)
committerJason A. Donenfeld <Jason@zx2c4.com>
Fri, 21 Dec 2018 15:42:16 +0000 (16:42 +0100)
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
23 files changed:
WireGuard/Shared/Model/Configuration.swift [deleted file]
WireGuard/Shared/Model/DNSServer.swift
WireGuard/Shared/Model/Endpoint.swift
WireGuard/Shared/Model/IPAddressRange.swift
WireGuard/Shared/Model/InterfaceConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyDNSServer.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyEndpoint.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyIPAddressRange.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyInterfaceConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyPeerConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/Model/Legacy/LegacyTunnelConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/Model/PeerConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/Model/TunnelConfiguration.swift [new file with mode: 0644]
WireGuard/Shared/NETunnelProviderProtocol+Extension.swift
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/ConfigFile/WgQuickConfigFileWriter.swift
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelDetailTableViewController.swift
WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift

diff --git a/WireGuard/Shared/Model/Configuration.swift b/WireGuard/Shared/Model/Configuration.swift
deleted file mode 100644 (file)
index 77dfe97..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018 WireGuard LLC. All Rights Reserved.
-
-import Foundation
-
-@available(OSX 10.14, iOS 12.0, *)
-final class TunnelConfiguration: Codable {
-    var interface: InterfaceConfiguration
-    let peers: [PeerConfiguration]
-
-    static let keyLength: Int = 32
-
-    init(interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
-        self.interface = interface
-        self.peers = peers
-
-        let peerPublicKeysArray = peers.map { $0.publicKey }
-        let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
-        if peerPublicKeysArray.count != peerPublicKeysSet.count {
-            fatalError("Two or more peers cannot have the same public key")
-        }
-    }
-}
-
-@available(OSX 10.14, iOS 12.0, *)
-struct InterfaceConfiguration: Codable {
-    var name: String
-    var privateKey: Data
-    var addresses = [IPAddressRange]()
-    var listenPort: UInt16?
-    var mtu: UInt16?
-    var dns = [DNSServer]()
-
-    init(name: String, privateKey: Data) {
-        self.name = name
-        self.privateKey = privateKey
-        if name.isEmpty {
-            fatalError("Empty name")
-        }
-        if privateKey.count != TunnelConfiguration.keyLength {
-            fatalError("Invalid private key")
-        }
-    }
-}
-
-@available(OSX 10.14, iOS 12.0, *)
-struct PeerConfiguration: Codable {
-    var publicKey: Data
-    var preSharedKey: Data? {
-        didSet(value) {
-            if let value = value {
-                if value.count != TunnelConfiguration.keyLength {
-                    fatalError("Invalid preshared key")
-                }
-            }
-        }
-    }
-    var allowedIPs = [IPAddressRange]()
-    var endpoint: Endpoint?
-    var persistentKeepAlive: UInt16?
-
-    init(publicKey: Data) {
-        self.publicKey = publicKey
-        if publicKey.count != TunnelConfiguration.keyLength {
-            fatalError("Invalid public key")
-        }
-    }
-}
index d1c75fc04a1f6929f89f3ed16cc88a850541bd11..8703fbba06e0cc5cd6fc174bbd3d0ee650e99ac0 100644 (file)
@@ -4,52 +4,50 @@
 import Foundation
 import Network
 
-@available(OSX 10.14, iOS 12.0, *)
 struct DNSServer {
     let address: IPAddress
-}
-
-// MARK: Converting to and from String
-
-extension DNSServer {
-    init?(from addressString: String) {
-        if let addr = IPv4Address(addressString) {
-            address = addr
-        } else if let addr = IPv6Address(addressString) {
-            address = addr
-        } else {
-            return nil
-        }
-    }
-    func stringRepresentation() -> String {
-        return "\(address)"
+    
+    init(address: IPAddress) {
+        self.address = address
     }
 }
 
-// MARK: Codable
-
-@available(OSX 10.14, iOS 12.0, *)
 extension DNSServer: Codable {
     public func encode(to encoder: Encoder) throws {
         var container = encoder.singleValueContainer()
-        try container.encode(address.rawValue)
+        try container.encode(stringRepresentation)
     }
+    
     public init(from decoder: Decoder) throws {
-        let container = try decoder.singleValueContainer()
-        var data = try container.decode(Data.self)
-        let ipAddressFromData: IPAddress? = {
-            switch data.count {
-            case 4: return IPv4Address(data)
-            case 16: return IPv6Address(data)
-            default: return nil
-            }
-        }()
-        guard let ipAddress = ipAddressFromData else {
+        let values = try decoder.singleValueContainer()
+        let addressString = try values.decode(String.self)
+        
+        if let address = IPv4Address(addressString) {
+            self.address = address
+        } else if let address = IPv6Address(addressString) {
+            self.address = address
+        } else {
             throw DecodingError.invalidData
         }
-        address = ipAddress
     }
+
     enum DecodingError: Error {
         case invalidData
     }
 }
+
+extension DNSServer {
+    var stringRepresentation: String {
+        return "\(address)"
+    }
+    
+    init?(from addressString: String) {
+        if let addr = IPv4Address(addressString) {
+            address = addr
+        } else if let addr = IPv6Address(addressString) {
+            address = addr
+        } else {
+            return nil
+        }
+    }
+}
index 3a4beee7551d6387b0627849ce7505ec57bf9268..891c5642365ceedad7c03f6d472d8f99deeba1a2 100644 (file)
@@ -4,15 +4,48 @@
 import Foundation
 import Network
 
-@available(OSX 10.14, iOS 12.0, *)
 struct Endpoint {
     let host: NWEndpoint.Host
     let port: NWEndpoint.Port
+    
+    init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
+        self.host = host
+        self.port = port
+    }
 }
 
-// MARK: Converting to and from String
+extension Endpoint: Codable {
+    init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        let endpointString = try container.decode(String.self)
+        guard let endpoint = Endpoint(from: endpointString) else {
+            throw DecodingError.invalidData
+        }
+        self = endpoint
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.singleValueContainer()
+        try container.encode(stringRepresentation)
+    }
+    
+    enum DecodingError: Error {
+        case invalidData
+    }
+}
 
 extension Endpoint {
+    var stringRepresentation: String {
+        switch host {
+        case .name(let hostname, _):
+            return "\(hostname):\(port)"
+        case .ipv4(let address):
+            return "\(address):\(port)"
+        case .ipv6(let address):
+            return "[\(address)]:\(port)"
+        }
+    }
+    
     init?(from string: String) {
         // Separation of host and port is based on 'parse_endpoint' function in
         // https://git.zx2c4.com/WireGuard/tree/src/tools/config.c
@@ -41,37 +74,6 @@ extension Endpoint {
         host = NWEndpoint.Host(hostString)
         port = endpointPort
     }
-    func stringRepresentation() -> String {
-        switch host {
-        case .name(let hostname, _):
-            return "\(hostname):\(port)"
-        case .ipv4(let address):
-            return "\(address):\(port)"
-        case .ipv6(let address):
-            return "[\(address)]:\(port)"
-        }
-    }
-}
-
-// MARK: Codable
-
-@available(OSX 10.14, iOS 12.0, *)
-extension Endpoint: Codable {
-    public func encode(to encoder: Encoder) throws {
-        var container = encoder.singleValueContainer()
-        try container.encode(stringRepresentation())
-    }
-    public init(from decoder: Decoder) throws {
-        let container = try decoder.singleValueContainer()
-        let endpointString = try container.decode(String.self)
-        guard let endpoint = Endpoint(from: endpointString) else {
-            throw DecodingError.invalidData
-        }
-        self = endpoint
-    }
-    enum DecodingError: Error {
-        case invalidData
-    }
 }
 
 extension Endpoint {
index 0098c32c8d7fcad6058405cf0f32489f0aa034cc..da4cbd5eee39cab48856e2543f9a57c096950e30 100644 (file)
@@ -4,16 +4,28 @@
 import Foundation
 import Network
 
-@available(OSX 10.14, iOS 12.0, *)
 struct IPAddressRange {
     let address: IPAddress
     var networkPrefixLength: UInt8
+    
+    init(address: IPAddress, networkPrefixLength: UInt8) {
+        self.address = address
+        self.networkPrefixLength = networkPrefixLength
+    }
 }
 
-// MARK: Converting to and from String
-
 extension IPAddressRange {
+    var stringRepresentation: String {
+        return "\(address)/\(networkPrefixLength)"
+    }
+    
     init?(from string: String) {
+        guard let parsed = IPAddressRange.parseAddressString(string) else { return nil }
+        address = parsed.0
+        networkPrefixLength = parsed.1
+    }
+    
+    private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? {
         let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex
         let addressString = String(string[string.startIndex ..< endOfIPAddress])
         let address: IPAddress
@@ -24,7 +36,8 @@ extension IPAddressRange {
         } else {
             return nil
         }
-        let maxNetworkPrefixLength: UInt8 = (address is IPv4Address) ? 32 : 128
+        
+        let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
         var networkPrefixLength: UInt8
         if endOfIPAddress < string.endIndex { // "/" was located
             let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
@@ -35,47 +48,25 @@ extension IPAddressRange {
         } else {
             networkPrefixLength = maxNetworkPrefixLength
         }
-        self.address = address
-        self.networkPrefixLength = networkPrefixLength
-    }
-    func stringRepresentation() -> String {
-        return "\(address)/\(networkPrefixLength)"
+        
+        return (address, networkPrefixLength)
     }
 }
 
-// MARK: Codable
-
-@available(OSX 10.14, iOS 12.0, *)
 extension IPAddressRange: Codable {
     public func encode(to encoder: Encoder) throws {
         var container = encoder.singleValueContainer()
-        let addressDataLength: Int
-        if address is IPv4Address {
-            addressDataLength = 4
-        } else if address is IPv6Address {
-            addressDataLength = 16
-        } else {
-            fatalError()
-        }
-        var data = Data(capacity: addressDataLength + 1)
-        data.append(address.rawValue)
-        data.append(networkPrefixLength)
-        try container.encode(data)
+        try container.encode(stringRepresentation)
     }
+    
     public init(from decoder: Decoder) throws {
-        let container = try decoder.singleValueContainer()
-        var data = try container.decode(Data.self)
-        networkPrefixLength = data.removeLast()
-        let ipAddressFromData: IPAddress? = {
-            switch data.count {
-            case 4: return IPv4Address(data)
-            case 16: return IPv6Address(data)
-            default: return nil
-            }
-        }()
-        guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
-        address = ipAddress
+        let values = try decoder.singleValueContainer()
+        let addressString = try values.decode(String.self)
+        guard let parsed = IPAddressRange.parseAddressString(addressString) else { throw DecodingError.invalidData }
+        address = parsed.0
+        networkPrefixLength = parsed.1
     }
+    
     enum DecodingError: Error {
         case invalidData
     }
diff --git a/WireGuard/Shared/Model/InterfaceConfiguration.swift b/WireGuard/Shared/Model/InterfaceConfiguration.swift
new file mode 100644 (file)
index 0000000..dfcb1fc
--- /dev/null
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+struct InterfaceConfiguration {
+    var name: String
+    var privateKey: Data
+    var addresses = [IPAddressRange]()
+    var listenPort: UInt16?
+    var mtu: UInt16?
+    var dns = [DNSServer]()
+    
+    init(name: String, privateKey: Data) {
+        self.name = name
+        self.privateKey = privateKey
+        if name.isEmpty {
+            fatalError("Empty name")
+        }
+        if privateKey.count != TunnelConfiguration.keyLength {
+            fatalError("Invalid private key")
+        }
+    }
+}
+
+extension InterfaceConfiguration: Codable {
+    enum CodingKeys: String, CodingKey {
+        case name = "Name"
+        case privateKey = "PrivateKey"
+        case addresses = "Address"
+        case listenPort = "ListenPort"
+        case mtu = "MTU"
+        case dns = "DNS"
+    }
+    
+    init(from decoder: Decoder) throws {
+        let values = try decoder.container(keyedBy: CodingKeys.self)
+        name = try values.decode(String.self, forKey: .name)
+        privateKey = try Data(base64Encoded: values.decode(String.self, forKey: .privateKey))!
+        addresses = try values.decode([IPAddressRange].self, forKey: .addresses)
+        listenPort = try? values.decode(UInt16.self, forKey: .listenPort)
+        mtu = try? values.decode(UInt16.self, forKey: .mtu)
+        dns = try values.decode([DNSServer].self, forKey: .dns)
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(name, forKey: .name)
+        try container.encode(privateKey.base64EncodedString(), forKey: .privateKey)
+        try container.encode(addresses, forKey: .addresses)
+        if let listenPort = listenPort {
+            try container.encode(listenPort, forKey: .listenPort)
+        }
+        if let mtu = mtu {
+            try container.encode(mtu, forKey: .mtu)
+        }
+        try container.encode(dns, forKey: .dns)
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyDNSServer.swift b/WireGuard/Shared/Model/Legacy/LegacyDNSServer.swift
new file mode 100644 (file)
index 0000000..f2afcd7
--- /dev/null
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+import Network
+
+struct LegacyDNSServer: Codable {
+    let address: IPAddress
+    
+    init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        var data = try container.decode(Data.self)
+        let ipAddressFromData: IPAddress? = {
+            switch data.count {
+            case 4: return IPv4Address(data)
+            case 16: return IPv6Address(data)
+            default: return nil
+            }
+        }()
+        guard let ipAddress = ipAddressFromData else {
+            throw DecodingError.invalidData
+        }
+        address = ipAddress
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.singleValueContainer()
+        try container.encode(address.rawValue)
+    }
+
+    enum DecodingError: Error {
+        case invalidData
+    }
+}
+
+extension LegacyDNSServer {
+    var migrated: DNSServer {
+        return DNSServer(address: address)
+    }
+}
+
+extension Array where Element == LegacyDNSServer {
+    var migrated: [DNSServer] {
+        return map { $0.migrated }
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyEndpoint.swift b/WireGuard/Shared/Model/Legacy/LegacyEndpoint.swift
new file mode 100644 (file)
index 0000000..7a80be4
--- /dev/null
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+import Network
+
+struct LegacyEndpoint: Codable {
+    let host: NWEndpoint.Host
+    let port: NWEndpoint.Port
+    
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        let endpointString = try container.decode(String.self)
+        guard !endpointString.isEmpty else { throw DecodingError.invalidData }
+        let startOfPort: String.Index
+        let hostString: String
+        if endpointString.first! == "[" {
+            // Look for IPv6-style endpoint, like [::1]:80
+            let startOfHost = endpointString.index(after: endpointString.startIndex)
+            guard let endOfHost = endpointString.dropFirst().firstIndex(of: "]") else { throw DecodingError.invalidData }
+            let afterEndOfHost = endpointString.index(after: endOfHost)
+            guard endpointString[afterEndOfHost] == ":" else { throw DecodingError.invalidData }
+            startOfPort = endpointString.index(after: afterEndOfHost)
+            hostString = String(endpointString[startOfHost ..< endOfHost])
+        } else {
+            // Look for an IPv4-style endpoint, like 127.0.0.1:80
+            guard let endOfHost = endpointString.firstIndex(of: ":") else { throw DecodingError.invalidData }
+            startOfPort = endpointString.index(after: endOfHost)
+            hostString = String(endpointString[endpointString.startIndex ..< endOfHost])
+        }
+        guard let endpointPort = NWEndpoint.Port(String(endpointString[startOfPort ..< endpointString.endIndex])) else { throw DecodingError.invalidData }
+        let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in
+            return !CharacterSet.urlHostAllowed.contains(char)
+        }
+        guard invalidCharacterIndex == nil else { throw DecodingError.invalidData }
+        host = NWEndpoint.Host(hostString)
+        port = endpointPort
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        let stringRepresentation: String
+        switch host {
+        case .name(let hostname, _):
+            stringRepresentation = "\(hostname):\(port)"
+        case .ipv4(let address):
+            stringRepresentation = "\(address):\(port)"
+        case .ipv6(let address):
+            stringRepresentation = "[\(address)]:\(port)"
+        }
+        
+        var container = encoder.singleValueContainer()
+        try container.encode(stringRepresentation)
+    }
+
+    enum DecodingError: Error {
+        case invalidData
+    }
+}
+
+extension LegacyEndpoint {
+    var migrated: Endpoint {
+        return Endpoint(host: host, port: port)
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyIPAddressRange.swift b/WireGuard/Shared/Model/Legacy/LegacyIPAddressRange.swift
new file mode 100644 (file)
index 0000000..ade87f2
--- /dev/null
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+import Network
+
+struct LegacyIPAddressRange: Codable {
+    let address: IPAddress
+    let networkPrefixLength: UInt8
+    
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        var data = try container.decode(Data.self)
+        networkPrefixLength = data.removeLast()
+        let ipAddressFromData: IPAddress? = {
+            switch data.count {
+            case 4: return IPv4Address(data)
+            case 16: return IPv6Address(data)
+            default: return nil
+            }
+        }()
+        guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
+        address = ipAddress
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.singleValueContainer()
+        let addressDataLength: Int
+        if address is IPv4Address {
+            addressDataLength = 4
+        } else if address is IPv6Address {
+            addressDataLength = 16
+        } else {
+            fatalError()
+        }
+        var data = Data(capacity: addressDataLength + 1)
+        data.append(address.rawValue)
+        data.append(networkPrefixLength)
+        try container.encode(data)
+    }
+
+    enum DecodingError: Error {
+        case invalidData
+    }
+}
+
+extension LegacyIPAddressRange {
+    var migrated: IPAddressRange {
+        return IPAddressRange(address: address, networkPrefixLength: networkPrefixLength)
+    }
+}
+
+extension Array where Element == LegacyIPAddressRange {
+    var migrated: [IPAddressRange] {
+        return map { $0.migrated }
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyInterfaceConfiguration.swift b/WireGuard/Shared/Model/Legacy/LegacyInterfaceConfiguration.swift
new file mode 100644 (file)
index 0000000..680c8d7
--- /dev/null
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+struct LegacyInterfaceConfiguration: Codable {
+    let name: String
+    let privateKey: Data
+    let addresses: [LegacyIPAddressRange]
+    let listenPort: UInt16?
+    let mtu: UInt16?
+    let dns: [LegacyDNSServer]
+}
+
+extension LegacyInterfaceConfiguration {
+    var migrated: InterfaceConfiguration {
+        var interface = InterfaceConfiguration(name: name, privateKey: privateKey)
+        interface.addresses = addresses.migrated
+        interface.listenPort = listenPort
+        interface.mtu = mtu
+        interface.dns = dns.migrated
+        return interface
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyPeerConfiguration.swift b/WireGuard/Shared/Model/Legacy/LegacyPeerConfiguration.swift
new file mode 100644 (file)
index 0000000..5a61b4a
--- /dev/null
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+struct LegacyPeerConfiguration: Codable {
+    let publicKey: Data
+    let preSharedKey: Data?
+    let allowedIPs: [LegacyIPAddressRange]
+    let endpoint: LegacyEndpoint?
+    let persistentKeepAlive: UInt16?
+}
+
+extension LegacyPeerConfiguration {
+    var migrated: PeerConfiguration {
+        var configuration = PeerConfiguration(publicKey: publicKey)
+        configuration.preSharedKey = preSharedKey
+        configuration.allowedIPs = allowedIPs.migrated
+        configuration.endpoint = endpoint?.migrated
+        configuration.persistentKeepAlive = persistentKeepAlive
+        return configuration
+    }
+}
+
+extension Array where Element == LegacyPeerConfiguration {
+    var migrated: [PeerConfiguration] {
+        return map { $0.migrated }
+    }
+}
diff --git a/WireGuard/Shared/Model/Legacy/LegacyTunnelConfiguration.swift b/WireGuard/Shared/Model/Legacy/LegacyTunnelConfiguration.swift
new file mode 100644 (file)
index 0000000..ac369b9
--- /dev/null
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+final class LegacyTunnelConfiguration: Codable {
+    let interface: LegacyInterfaceConfiguration
+    let peers: [LegacyPeerConfiguration]
+}
+
+extension LegacyTunnelConfiguration {
+    var migrated: TunnelConfiguration {
+        return TunnelConfiguration(interface: interface.migrated, peers: peers.migrated)
+    }
+}
diff --git a/WireGuard/Shared/Model/PeerConfiguration.swift b/WireGuard/Shared/Model/PeerConfiguration.swift
new file mode 100644 (file)
index 0000000..0fad842
--- /dev/null
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+struct PeerConfiguration {
+    var publicKey: Data
+    var preSharedKey: Data? {
+        didSet(value) {
+            if let value = value {
+                if value.count != TunnelConfiguration.keyLength {
+                    fatalError("Invalid preshared key")
+                }
+            }
+        }
+    }
+    var allowedIPs = [IPAddressRange]()
+    var endpoint: Endpoint?
+    var persistentKeepAlive: UInt16?
+    
+    init(publicKey: Data) {
+        self.publicKey = publicKey
+        if publicKey.count != TunnelConfiguration.keyLength {
+            fatalError("Invalid public key")
+        }
+    }
+}
+
+extension PeerConfiguration: Codable {
+    enum CodingKeys: String, CodingKey {
+        case publicKey = "PublicKey"
+        case preSharedKey = "PreSharedKey"
+        case allowedIPs = "AllowedIPs"
+        case endpoint = "Endpoint"
+        case persistentKeepAlive = "PersistentKeepAlive"
+    }
+    
+    init(from decoder: Decoder) throws {
+        let values = try decoder.container(keyedBy: CodingKeys.self)
+        publicKey = try Data(base64Encoded: values.decode(String.self, forKey: .publicKey))!
+        if let base64PreSharedKey = try? values.decode(Data.self, forKey: .preSharedKey) {
+            preSharedKey = Data(base64Encoded: base64PreSharedKey)
+        } else {
+            preSharedKey = nil
+        }
+        allowedIPs = try values.decode([IPAddressRange].self, forKey: .allowedIPs)
+        endpoint = try? values.decode(Endpoint.self, forKey: .endpoint)
+        persistentKeepAlive = try? values.decode(UInt16.self, forKey: .persistentKeepAlive)
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(publicKey.base64EncodedString(), forKey: .publicKey)
+        if let preSharedKey = preSharedKey {
+            try container.encode(preSharedKey.base64EncodedString(), forKey: .preSharedKey)
+        }
+        
+        try container.encode(allowedIPs, forKey: .allowedIPs)
+        if let endpoint = endpoint {
+            try container.encode(endpoint, forKey: .endpoint)
+        }
+        if let persistentKeepAlive = persistentKeepAlive {
+            try container.encode(persistentKeepAlive, forKey: .persistentKeepAlive)
+        }
+    }
+}
diff --git a/WireGuard/Shared/Model/TunnelConfiguration.swift b/WireGuard/Shared/Model/TunnelConfiguration.swift
new file mode 100644 (file)
index 0000000..87812cd
--- /dev/null
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018 WireGuard LLC. All Rights Reserved.
+
+import Foundation
+
+final class TunnelConfiguration {
+    var interface: InterfaceConfiguration
+    let peers: [PeerConfiguration]
+
+    static let keyLength = 32
+
+    init(interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
+        self.interface = interface
+        self.peers = peers
+
+        let peerPublicKeysArray = peers.map { $0.publicKey }
+        let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
+        if peerPublicKeysArray.count != peerPublicKeysSet.count {
+            fatalError("Two or more peers cannot have the same public key")
+        }
+    }
+}
+
+extension TunnelConfiguration: Codable {
+    enum CodingKeys: String, CodingKey {
+        case interface = "Interface"
+        case peers = "Peer"
+    }
+    
+    convenience init(from decoder: Decoder) throws {
+        let values = try decoder.container(keyedBy: CodingKeys.self)
+        let interface = try values.decode(InterfaceConfiguration.self, forKey: .interface)
+        let peers = try values.decode([PeerConfiguration].self, forKey: .peers)
+        self.init(interface: interface, peers: peers)
+    }
+    
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(interface, forKey: .interface)
+        try container.encode(peers, forKey: .peers)
+    }
+}
index 776e03b26ec57b4a6d523726c4c52d01c23738d5..e5cfac884f80da00223b4e4ba70c75ccc78fcffd 100644 (file)
@@ -3,24 +3,53 @@
 
 import NetworkExtension
 
+let tunnelConfigurationVersion = 2
+
 extension NETunnelProviderProtocol {
+    
+    enum Keys: String {
+        case tunnelConfiguration = "TunnelConfiguration"
+        case tunnelConfigurationVersion = "TunnelConfigurationVersion"
+        case isActivateOnDemandEnabled = "IsActivateOnDemandEnabled"
+    }
+    
+    var tunnelConfiguration: TunnelConfiguration? {
+        migrateConfigurationIfNeeded()
+
+        let tunnelConfigurationData: Data?
+        if let configurationDictionary = providerConfiguration?[Keys.tunnelConfiguration.rawValue] {
+            tunnelConfigurationData = try? JSONSerialization.data(withJSONObject: configurationDictionary, options: [])
+        } else {
+            tunnelConfigurationData = nil
+        }
+        
+        guard tunnelConfigurationData != nil else { return nil }
+        return try? JSONDecoder().decode(TunnelConfiguration.self, from: tunnelConfigurationData!)
+    }
+    
+    var isActivateOnDemandEnabled: Bool {
+        return providerConfiguration?[Keys.isActivateOnDemandEnabled.rawValue] as? Bool ?? false
+    }
+    
     convenience init?(tunnelConfiguration: TunnelConfiguration, isActivateOnDemandEnabled: Bool) {
         assert(!tunnelConfiguration.interface.name.isEmpty)
-        guard let serializedTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
-
+        
+        guard let tunnelConfigData = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
+        guard let tunnelConfigDictionary = try? JSONSerialization.jsonObject(with: tunnelConfigData, options: .allowFragments) else { return nil }
+        
         self.init()
 
         let appId = Bundle.main.bundleIdentifier!
         providerBundleIdentifier = "\(appId).network-extension"
         providerConfiguration = [
-            "tunnelConfiguration": serializedTunnelConfiguration,
-            "tunnelConfigurationVersion": 1,
-            "isActivateOnDemandEnabled": isActivateOnDemandEnabled
+            Keys.tunnelConfiguration.rawValue: tunnelConfigDictionary,
+            Keys.tunnelConfigurationVersion.rawValue: tunnelConfigurationVersion,
+            Keys.isActivateOnDemandEnabled.rawValue: isActivateOnDemandEnabled
         ]
 
-        let endpoints = tunnelConfiguration.peers.compactMap {$0.endpoint}
+        let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
         if endpoints.count == 1 {
-            serverAddress = endpoints.first!.stringRepresentation()
+            serverAddress = endpoints[0].stringRepresentation
         } else if endpoints.isEmpty {
             serverAddress = "Unspecified"
         } else {
@@ -29,18 +58,42 @@ extension NETunnelProviderProtocol {
         username = tunnelConfiguration.interface.name
     }
 
-    func tunnelConfiguration() -> TunnelConfiguration? {
-        guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return nil }
-        return try? JSONDecoder().decode(TunnelConfiguration.self, from: serializedTunnelConfiguration)
-    }
-
-    var isActivateOnDemandEnabled: Bool {
-        return (providerConfiguration?["isActivateOnDemandEnabled"] as? Bool) ?? false
-    }
-
     func hasTunnelConfiguration(tunnelConfiguration otherTunnelConfiguration: TunnelConfiguration) -> Bool {
-        guard let serializedThisTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return false }
+        guard let serializedThisTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return false }
         guard let serializedOtherTunnelConfiguration = try? JSONEncoder().encode(otherTunnelConfiguration) else { return false }
         return serializedThisTunnelConfiguration == serializedOtherTunnelConfiguration
     }
+    
+    @discardableResult
+    func migrateConfigurationIfNeeded() -> Bool {
+        guard let providerConfiguration = providerConfiguration else { return false }
+        guard let configurationVersion = providerConfiguration[Keys.tunnelConfigurationVersion.rawValue] as? Int ?? providerConfiguration["tunnelConfigurationVersion"] as? Int else { return false }
+        
+        if configurationVersion < tunnelConfigurationVersion {
+            switch configurationVersion {
+            case 1:
+                migrateFromConfigurationV1()
+            default:
+                fatalError("No migration from configuration version \(configurationVersion) exists.")
+            }
+            return true
+        }
+        
+        return false
+    }
+    
+    private func migrateFromConfigurationV1() {
+        guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return }
+        guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return }
+        guard let isActivateOnDemandEnabled = providerConfiguration?["isActivateOnDemandEnabled"] as? Bool else { return }
+        guard let tunnelConfigData = try? JSONEncoder().encode(configuration.migrated) else { return }
+        guard let tunnelConfigDictionary = try? JSONSerialization.jsonObject(with: tunnelConfigData, options: .allowFragments) else { return }
+        
+        providerConfiguration = [
+            Keys.tunnelConfiguration.rawValue: tunnelConfigDictionary,
+            Keys.tunnelConfigurationVersion.rawValue: tunnelConfigurationVersion,
+            Keys.isActivateOnDemandEnabled.rawValue: isActivateOnDemandEnabled
+        ]
+    }
+    
 }
index 24c59c5c3b5560f7b1a73121eabf09ce8d4d36c6..782f2bd10393e6083cbbd7607c274884a409309b 100644 (file)
                5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
                5F4541AE21C7704300994C13 /* NEVPNStatus+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541AD21C7704300994C13 /* NEVPNStatus+CustomStringConvertible.swift */; };
                5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541B121CBFAEE00994C13 /* String+ArrayConversion.swift */; };
+               5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */; };
+               5FF7B96321CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */; };
+               5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */; };
+               5FF7B96621CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */; };
+               5FF7B96E21CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96821CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift */; };
+               5FF7B96F21CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96821CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift */; };
+               5FF7B97021CC967B00A7DD74 /* LegacyIPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96921CC967B00A7DD74 /* LegacyIPAddressRange.swift */; };
+               5FF7B97121CC967B00A7DD74 /* LegacyIPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96921CC967B00A7DD74 /* LegacyIPAddressRange.swift */; };
+               5FF7B97221CC967B00A7DD74 /* LegacyEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96A21CC967B00A7DD74 /* LegacyEndpoint.swift */; };
+               5FF7B97321CC967B00A7DD74 /* LegacyEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96A21CC967B00A7DD74 /* LegacyEndpoint.swift */; };
+               5FF7B97421CC967B00A7DD74 /* LegacyDNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96B21CC967B00A7DD74 /* LegacyDNSServer.swift */; };
+               5FF7B97521CC967B00A7DD74 /* LegacyDNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96B21CC967B00A7DD74 /* LegacyDNSServer.swift */; };
+               5FF7B97621CC967B00A7DD74 /* LegacyPeerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96C21CC967B00A7DD74 /* LegacyPeerConfiguration.swift */; };
+               5FF7B97721CC967B00A7DD74 /* LegacyPeerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96C21CC967B00A7DD74 /* LegacyPeerConfiguration.swift */; };
+               5FF7B97821CC967B00A7DD74 /* LegacyTunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96D21CC967B00A7DD74 /* LegacyTunnelConfiguration.swift */; };
+               5FF7B97921CC967B00A7DD74 /* LegacyTunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF7B96D21CC967B00A7DD74 /* LegacyTunnelConfiguration.swift */; };
                6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
                6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
                6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
@@ -34,7 +50,7 @@
                6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
                6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
                6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
-               6F7774E82172020C006A79B3 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.swift */; };
+               6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* TunnelConfiguration.swift */; };
                6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E9217229DB006A79B3 /* IPAddressRange.swift */; };
                6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */; };
                6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */; };
@@ -64,7 +80,7 @@
                6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC1E211EC472002C96EB /* Assets.xcassets */; };
                6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */; };
                6FFA5D8921942F320001E2F7 /* PacketTunnelSettingsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */; };
-               6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.swift */; };
+               6FFA5D8E2194370D0001E2F7 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* TunnelConfiguration.swift */; };
                6FFA5D8F2194370D0001E2F7 /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E9217229DB006A79B3 /* IPAddressRange.swift */; };
                6FFA5D902194370D0001E2F7 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
                6FFA5D912194370D0001E2F7 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; };
                5F4541A821C451D100994C13 /* TunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatus.swift; sourceTree = "<group>"; };
                5F4541AD21C7704300994C13 /* NEVPNStatus+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEVPNStatus+CustomStringConvertible.swift"; sourceTree = "<group>"; };
                5F4541B121CBFAEE00994C13 /* String+ArrayConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ArrayConversion.swift"; sourceTree = "<group>"; };
+               5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceConfiguration.swift; sourceTree = "<group>"; };
+               5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerConfiguration.swift; sourceTree = "<group>"; };
+               5FF7B96821CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyInterfaceConfiguration.swift; sourceTree = "<group>"; };
+               5FF7B96921CC967B00A7DD74 /* LegacyIPAddressRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyIPAddressRange.swift; sourceTree = "<group>"; };
+               5FF7B96A21CC967B00A7DD74 /* LegacyEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyEndpoint.swift; sourceTree = "<group>"; };
+               5FF7B96B21CC967B00A7DD74 /* LegacyDNSServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyDNSServer.swift; sourceTree = "<group>"; };
+               5FF7B96C21CC967B00A7DD74 /* LegacyPeerConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPeerConfiguration.swift; sourceTree = "<group>"; };
+               5FF7B96D21CC967B00A7DD74 /* LegacyTunnelConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyTunnelConfiguration.swift; sourceTree = "<group>"; };
                6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = "<group>"; };
                6F5D0C1421832391000F85AD /* DNSResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = "<group>"; };
                6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WireGuardNetworkExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
                6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
                6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
                6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
-               6F7774E72172020C006A79B3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
+               6F7774E72172020C006A79B3 /* TunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelConfiguration.swift; sourceTree = "<group>"; };
                6F7774E9217229DB006A79B3 /* IPAddressRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPAddressRange.swift; sourceTree = "<group>"; };
                6F7774EE21722D97006A79B3 /* TunnelsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsManager.swift; sourceTree = "<group>"; };
                6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditTableViewController.swift; sourceTree = "<group>"; };
                        path = ViewController;
                        sourceTree = "<group>";
                };
+               5FF7B96721CC966300A7DD74 /* Legacy */ = {
+                       isa = PBXGroup;
+                       children = (
+                               5FF7B96B21CC967B00A7DD74 /* LegacyDNSServer.swift */,
+                               5FF7B96A21CC967B00A7DD74 /* LegacyEndpoint.swift */,
+                               5FF7B96821CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift */,
+                               5FF7B96921CC967B00A7DD74 /* LegacyIPAddressRange.swift */,
+                               5FF7B96C21CC967B00A7DD74 /* LegacyPeerConfiguration.swift */,
+                               5FF7B96D21CC967B00A7DD74 /* LegacyTunnelConfiguration.swift */,
+                       );
+                       path = Legacy;
+                       sourceTree = "<group>";
+               };
                6F5D0C1B218352EF000F85AD /* WireGuardNetworkExtension */ = {
                        isa = PBXGroup;
                        children = (
                6F7774E6217201E0006A79B3 /* Model */ = {
                        isa = PBXGroup;
                        children = (
-                               6F7774E72172020C006A79B3 /* Configuration.swift */,
+                               5FF7B96721CC966300A7DD74 /* Legacy */,
+                               6F7774E72172020C006A79B3 /* TunnelConfiguration.swift */,
                                6F7774E9217229DB006A79B3 /* IPAddressRange.swift */,
                                6F693A552179E556008551C1 /* Endpoint.swift */,
                                6F628C3E217F3413003482A3 /* DNSServer.swift */,
+                               5FF7B96121CC95DE00A7DD74 /* InterfaceConfiguration.swift */,
+                               5FF7B96421CC95FA00A7DD74 /* PeerConfiguration.swift */,
                        );
                        path = Model;
                        sourceTree = "<group>";
                                6FF3527121C240160008484E /* Logger.swift in Sources */,
                                6F5A2B4621AFDED40081EDD8 /* FileManager+Extension.swift in Sources */,
                                6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */,
+                               5FF7B97521CC967B00A7DD74 /* LegacyDNSServer.swift in Sources */,
                                6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
-                               6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */,
+                               5FF7B97921CC967B00A7DD74 /* LegacyTunnelConfiguration.swift in Sources */,
+                               6FFA5D8E2194370D0001E2F7 /* TunnelConfiguration.swift in Sources */,
+                               5FF7B97721CC967B00A7DD74 /* LegacyPeerConfiguration.swift in Sources */,
+                               5FF7B96621CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
                                6FFA5D8F2194370D0001E2F7 /* IPAddressRange.swift in Sources */,
                                6FFA5D902194370D0001E2F7 /* Endpoint.swift in Sources */,
+                               5FF7B96321CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
                                6FFA5D9321943BC90001E2F7 /* DNSResolver.swift in Sources */,
+                               5FF7B97121CC967B00A7DD74 /* LegacyIPAddressRange.swift in Sources */,
+                               5FF7B96F21CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift in Sources */,
                                6FFA5D912194370D0001E2F7 /* DNSServer.swift in Sources */,
                                6FFA5D8921942F320001E2F7 /* PacketTunnelSettingsGenerator.swift in Sources */,
+                               5FF7B97321CC967B00A7DD74 /* LegacyEndpoint.swift in Sources */,
                                6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                                6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
                                6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
                                5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
+                               5FF7B97821CC967B00A7DD74 /* LegacyTunnelConfiguration.swift in Sources */,
                                5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
+                               5FF7B97221CC967B00A7DD74 /* LegacyEndpoint.swift in Sources */,
                                6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
                                6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
                                6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
                                6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
+                               5FF7B97021CC967B00A7DD74 /* LegacyIPAddressRange.swift in Sources */,
+                               5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
                                5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
+                               5FF7B96E21CC967B00A7DD74 /* LegacyInterfaceConfiguration.swift in Sources */,
                                6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
                                6F6899A62180447E0012E523 /* x25519.c in Sources */,
                                6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
                                6FDEF8082187442100D8FBF6 /* WgQuickConfigFileWriter.swift in Sources */,
                                6FE254FB219C10800028284D /* ZipImporter.swift in Sources */,
                                6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
+                               5FF7B97621CC967B00A7DD74 /* LegacyPeerConfiguration.swift in Sources */,
                                5F4541AE21C7704300994C13 /* NEVPNStatus+CustomStringConvertible.swift in Sources */,
-                               6F7774E82172020C006A79B3 /* Configuration.swift in Sources */,
+                               6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
+                               5FF7B97421CC967B00A7DD74 /* LegacyDNSServer.swift in Sources */,
                                6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
                                6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
                                5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
                                6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */,
                                6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
                                5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
+                               5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
                                6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
                                6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
                                5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
index 78c4f8ba59b15318e8e6727a2de6be783d0b85ac..2dab266362404382487b4e727c98a938107859bc 100644 (file)
@@ -12,11 +12,11 @@ class WgQuickConfigFileWriter {
             output.append("ListenPort = \(listenPort)\n")
         }
         if !interface.addresses.isEmpty {
-            let addressString = interface.addresses.map { $0.stringRepresentation() }.joined(separator: ", ")
+            let addressString = interface.addresses.map { $0.stringRepresentation }.joined(separator: ", ")
             output.append("Address = \(addressString)\n")
         }
         if !interface.dns.isEmpty {
-            let dnsString = interface.dns.map { $0.stringRepresentation() }.joined(separator: ", ")
+            let dnsString = interface.dns.map { $0.stringRepresentation }.joined(separator: ", ")
             output.append("DNS = \(dnsString)\n")
         }
         if let mtu = interface.mtu {
@@ -30,11 +30,11 @@ class WgQuickConfigFileWriter {
                 output.append("PresharedKey = \(preSharedKey.base64EncodedString())\n")
             }
             if !peer.allowedIPs.isEmpty {
-                let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation() }.joined(separator: ", ")
+                let allowedIPsString = peer.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")
                 output.append("AllowedIPs = \(allowedIPsString)\n")
             }
             if let endpoint = peer.endpoint {
-                output.append("Endpoint = \(endpoint.stringRepresentation())\n")
+                output.append("Endpoint = \(endpoint.stringRepresentation)\n")
             }
             if let persistentKeepAlive = peer.persistentKeepAlive {
                 output.append("PersistentKeepalive = \(persistentKeepAlive)\n")
index f6dcc3276b7629f35727292205efd7243a59af30..b56cb374fb6ea642d20869aa0d63c02f978ff01c 100644 (file)
@@ -41,7 +41,14 @@ class TunnelsManager {
                 completionHandler(.failure(TunnelsManagerError.systemErrorOnListingTunnels(systemError: error)))
                 return
             }
-            completionHandler(.success(TunnelsManager(tunnelProviders: managers ?? [])))
+            
+            let tunnelManagers = managers ?? []
+            tunnelManagers.forEach {
+                if ($0.protocolConfiguration as? NETunnelProviderProtocol)?.migrateConfigurationIfNeeded() == true {
+                    $0.saveToPreferences { _ in }
+                }
+            }
+            completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
         }
         #endif
     }
@@ -309,6 +316,7 @@ class TunnelsManager {
             NotificationCenter.default.removeObserver(statusObservationToken)
         }
     }
+
 }
 
 class TunnelContainer: NSObject {
@@ -344,6 +352,14 @@ class TunnelContainer: NSObject {
     fileprivate let tunnelProvider: NETunnelProviderManager
     private var lastTunnelConnectionStatus: NEVPNStatus?
 
+    var tunnelConfiguration: TunnelConfiguration? {
+        return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration
+    }
+    
+    var activateOnDemandSetting: ActivateOnDemandSetting {
+        return ActivateOnDemandSetting(from: tunnelProvider)
+    }
+    
     init(tunnel: NETunnelProviderManager) {
         name = tunnel.localizedDescription ?? "Unnamed"
         let status = TunnelStatus(from: tunnel.connection.status)
@@ -353,14 +369,6 @@ class TunnelContainer: NSObject {
         super.init()
     }
 
-    func tunnelConfiguration() -> TunnelConfiguration? {
-        return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration()
-    }
-
-    func activateOnDemandSetting() -> ActivateOnDemandSetting {
-        return ActivateOnDemandSetting(from: tunnelProvider)
-    }
-
     func refreshStatus() {
         let status = TunnelStatus(from: tunnelProvider.connection.status)
         self.status = status
index f7ebb686bb839afcd2a5679d565ec5abfa86d31d..0b5b8c02f4743166369cc706357192c8c5c8c0a0 100644 (file)
@@ -106,7 +106,7 @@ class TunnelViewModel {
             scratchpad[.privateKey] = config.privateKey.base64EncodedString()
             scratchpad[.publicKey] = config.publicKey.base64EncodedString()
             if !config.addresses.isEmpty {
-                scratchpad[.addresses] = config.addresses.map { $0.stringRepresentation() }.joined(separator: ", ")
+                scratchpad[.addresses] = config.addresses.map { $0.stringRepresentation }.joined(separator: ", ")
             }
             if let listenPort = config.listenPort {
                 scratchpad[.listenPort] = String(listenPort)
@@ -115,7 +115,7 @@ class TunnelViewModel {
                 scratchpad[.mtu] = String(mtu)
             }
             if !config.dns.isEmpty {
-                scratchpad[.dns] = config.dns.map { $0.stringRepresentation() }.joined(separator: ", ")
+                scratchpad[.dns] = config.dns.map { $0.stringRepresentation }.joined(separator: ", ")
             }
         }
 
@@ -248,10 +248,10 @@ class TunnelViewModel {
                 scratchpad[.preSharedKey] = preSharedKey.base64EncodedString()
             }
             if !config.allowedIPs.isEmpty {
-                scratchpad[.allowedIPs] = config.allowedIPs.map { $0.stringRepresentation() }.joined(separator: ", ")
+                scratchpad[.allowedIPs] = config.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ")
             }
             if let endpoint = config.endpoint {
-                scratchpad[.endpoint] = endpoint.stringRepresentation()
+                scratchpad[.endpoint] = endpoint.stringRepresentation
             }
             if let persistentKeepAlive = config.persistentKeepAlive {
                 scratchpad[.persistentKeepAlive] = String(persistentKeepAlive)
index 22edcbc64a44e14e5000c9249a6a1f46566024ad..426e0534290d2479949823fbdccaf639a6d7abda 100644 (file)
@@ -93,7 +93,7 @@ class SettingsTableViewController: UITableViewController {
         _ = FileManager.deleteFile(at: destinationURL)
 
         let count = tunnelsManager.numberOfTunnels()
-        let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration() }
+        let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
         ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
             if let error = error {
                 ErrorPresenter.showErrorAlert(error: error, from: self)
index 50c0e33218b717192c4e005d07a3a56a3f13a73b..8378311352e9de47b0e274fecd5bdc861287ae64 100644 (file)
@@ -35,7 +35,7 @@ class TunnelDetailTableViewController: UITableViewController {
     init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
         self.tunnelsManager = tunnelsManager
         self.tunnel = tunnel
-        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
+        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
         super.init(style: .grouped)
         loadSections()
     }
@@ -101,7 +101,7 @@ class TunnelDetailTableViewController: UITableViewController {
 
 extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate {
     func tunnelSaved(tunnel: TunnelContainer) {
-        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
+        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
         loadSections()
         title = tunnel.name
         restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
@@ -229,9 +229,9 @@ extension TunnelDetailTableViewController {
     private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
         cell.key = tr("tunnelOnDemandKey")
-        cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
+        cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
         onDemandStatusObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
-            cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting())
+            cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
         }
         return cell
     }
index 3d9724c19db955d9b93eb75d71c486b75473e468..79dc7b659841da8c6c8775ebdaaf6659e8cb7a78 100644 (file)
@@ -49,8 +49,8 @@ class TunnelEditTableViewController: UITableViewController {
         // Use this initializer to edit an existing tunnel.
         self.tunnelsManager = tunnelsManager
         self.tunnel = tunnel
-        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
-        activateOnDemandSetting = tunnel.activateOnDemandSetting()
+        tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
+        activateOnDemandSetting = tunnel.activateOnDemandSetting
         super.init(style: .grouped)
         loadSections()
     }
index 3a9066d37cc4952edfbe22e0556dda194bd5eab4..f32a00445822c3104b7e09ad563da43de209d041 100644 (file)
@@ -26,12 +26,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
 
     //swiftlint:disable:next function_body_length
     override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
-
         let activationAttemptId = options?["activationAttemptId"] as? String
         let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId, tunnelProvider: self)
 
         guard let tunnelProviderProtocol = protocolConfiguration as? NETunnelProviderProtocol,
-            let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
+            let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration else {
                 errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
                 startTunnelCompletionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
                 return
index fd706d90a40aa6cfe25e9438fcef1cfadadb345c..462d11061e05b5137c7f54222924d0c5cc7fa45c 100644 (file)
@@ -6,7 +6,6 @@ import Network
 import NetworkExtension
 
 class PacketTunnelSettingsGenerator {
-
     let tunnelConfiguration: TunnelConfiguration
     let resolvedEndpoints: [Endpoint?]
 
@@ -26,7 +25,7 @@ class PacketTunnelSettingsGenerator {
             wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
             if let endpoint = resolvedEndpoints[index] {
                 if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
-                wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
+                wgSettings.append("endpoint=\(endpoint.stringRepresentation)\n")
             }
         }
 
@@ -51,13 +50,13 @@ class PacketTunnelSettingsGenerator {
             }
             if let endpoint = resolvedEndpoints[index] {
                 if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
-                wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
+                wgSettings.append("endpoint=\(endpoint.stringRepresentation)\n")
             }
             let persistentKeepAlive = peer.persistentKeepAlive ?? 0
             wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n")
             if !peer.allowedIPs.isEmpty {
                 wgSettings.append("replace_allowed_ips=true\n")
-                peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation())\n") }
+                peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") }
             }
         }
         return wgSettings
@@ -85,7 +84,7 @@ class PacketTunnelSettingsGenerator {
 
         let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
 
-        let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation() }
+        let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation }
         let dnsSettings = NEDNSSettings(servers: dnsServerStrings)
         dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS
         networkSettings.dnsSettings = dnsSettings