-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#3: Add common extensions for VPN config
- Loading branch information
1 parent
1ab2459
commit 6c07409
Showing
7 changed files
with
468 additions
and
0 deletions.
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
...NCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Extensions/Foundation/Serializer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// | ||
// Serializer.swift | ||
// SOLARdVPNCommunityCoreiOS | ||
// | ||
// Created by Lika Vorobeva on 28.09.2022. | ||
// | ||
|
||
import Foundation | ||
|
||
enum Serializer { | ||
static func toData<T: Encodable>(from object: T) -> Data? { | ||
if let data = try? JSONEncoder().encode(object) { | ||
return data | ||
} | ||
if let dataConvertible = object as? DataConvertible, let data = dataConvertible.data { | ||
return data | ||
} | ||
return nil | ||
} | ||
|
||
static func fromData<T: Decodable>(_ data: Data, withType type: T.Type) -> T? { | ||
if let object = try? JSONDecoder().decode(type.self, from: data) { | ||
return object | ||
} | ||
if let Type = T.self as? DataConvertible.Type, let object = Type.init(data: data) as? T { | ||
return object | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
// MARK: - DataConvertible | ||
|
||
protocol DataConvertible { | ||
init?(data: Data) | ||
var data: Data? { get } | ||
} | ||
|
||
extension DataConvertible { | ||
init?(data: Data) { | ||
guard data.count == MemoryLayout<Self>.size else { return nil } | ||
self = data.withUnsafeBytes { $0.load(as: Self.self) } | ||
} | ||
|
||
var data: Data? { | ||
return withUnsafeBytes(of: self) { Data($0) } | ||
} | ||
} | ||
|
||
// MARK: - DataConvertible implementation | ||
|
||
extension Int: DataConvertible { } | ||
extension Float: DataConvertible { } | ||
extension Double: DataConvertible { } | ||
extension Bool: DataConvertible { } | ||
extension String: DataConvertible { | ||
init?(data: Data) { | ||
self.init(data: data, encoding: .utf8) | ||
} | ||
|
||
var data: Data? { | ||
return data(using: .utf8) | ||
} | ||
} | ||
|
||
extension Encodable { | ||
func toData() -> Data? { | ||
return Serializer.toData(from: self) | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
...CoreiOS/SOLARdVPNCommunityCoreiOS/Common/Extensions/VPN/NETunnelProviderManager+Ext.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// NETunnelProviderManager+Ext.swift | ||
// SOLARdVPNCommunityCoreiOS | ||
// | ||
// Created by Lika Vorobeva on 28.09.2022. | ||
// | ||
|
||
import Foundation | ||
import WireGuardKit | ||
import NetworkExtension | ||
|
||
extension NETunnelProviderManager { | ||
private static var cachedKey: UInt8 = 0 | ||
|
||
var providerSession: NETunnelProviderSession? { | ||
connection as? NETunnelProviderSession | ||
} | ||
|
||
var provider: NETunnelProviderProtocol? { | ||
protocolConfiguration as? NETunnelProviderProtocol | ||
} | ||
|
||
var tunnelConfiguration: TunnelConfiguration? { | ||
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedKey) as? TunnelConfiguration { | ||
return cached | ||
} | ||
|
||
guard let provider = protocolConfiguration as? NETunnelProviderProtocol, | ||
let config = provider.asTunnelConfiguration(with: localizedDescription) else { | ||
return nil | ||
} | ||
|
||
objc_setAssociatedObject( | ||
self, &NETunnelProviderManager.cachedKey, | ||
config, | ||
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC | ||
) | ||
|
||
return config | ||
} | ||
|
||
func set(tunnelConfiguration: TunnelConfiguration) { | ||
protocolConfiguration = NETunnelProviderProtocol( | ||
tunnelConfiguration: tunnelConfiguration, | ||
previouslyFrom: protocolConfiguration | ||
) | ||
localizedDescription = tunnelConfiguration.name | ||
objc_setAssociatedObject( | ||
self, &NETunnelProviderManager.cachedKey, | ||
tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC | ||
) | ||
} | ||
|
||
func isEquivalent(to tunnel: TunnelContainer) -> Bool { | ||
localizedDescription == tunnel.name && tunnelConfiguration == tunnel.tunnelConfiguration | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
...PNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Extensions/VPN/NEVPNManager+Ext.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// NEVPNManager+Ext.swift | ||
// SOLARdVPNCommunityCoreiOS | ||
// | ||
// Created by Lika Vorobeva on 28.09.2022. | ||
// | ||
|
||
import NetworkExtension | ||
|
||
extension NEVPNManager { | ||
var tunnelBundleIdentifier: String? { | ||
guard let proto = protocolConfiguration as? NETunnelProviderProtocol else { | ||
return nil | ||
} | ||
return proto.providerBundleIdentifier | ||
} | ||
|
||
func isTunnel(withIdentifier bundleIdentifier: String) -> Bool { | ||
return tunnelBundleIdentifier == bundleIdentifier | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
...VPNCommunityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Extensions/VPN/NEVPNStatus+Ext.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// NEVPNStatus+Ext.swift | ||
// SOLARdVPNCommunityCoreiOS | ||
// | ||
// Created by Lika Vorobeva on 28.09.2022. | ||
// | ||
|
||
import Foundation | ||
import NetworkExtension | ||
|
||
extension NEVPNStatus { | ||
var description: String { | ||
switch self { | ||
case .connected: | ||
return "connected" | ||
case .connecting: | ||
return "connecting" | ||
case .disconnected: | ||
return "disconnected" | ||
case .disconnecting: | ||
return "disconnecting" | ||
case .invalid: | ||
return "invalid" | ||
case .reasserting: | ||
return "reasserting" | ||
@unknown default: | ||
return "@unknown default" | ||
} | ||
} | ||
} |
202 changes: 202 additions & 0 deletions
202
...nityCoreiOS/SOLARdVPNCommunityCoreiOS/Common/Extensions/VPN/TunnelConfiguration+Ext.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
// | ||
// TunnelConfiguration+Ext.swift | ||
// SOLARdVPNCommunityCoreiOS | ||
// | ||
// Created by Lika Vorobeva on 28.09.2022. | ||
// | ||
|
||
import Foundation | ||
import WireGuardKit | ||
|
||
private struct Constants { | ||
let interfaceSectionKeys: Set<String> = ["private_key", "listen_port", "fwmark"] | ||
let peerSectionKeys: Set<String> = [ | ||
"public_key", "preshared_key", "allowed_ip", "endpoint", | ||
"persistent_keepalive_interval", "last_handshake_time_sec", "last_handshake_time_nsec", | ||
"rx_bytes", "tx_bytes", "protocol_version" | ||
] | ||
} | ||
|
||
private let constants = Constants() | ||
|
||
extension TunnelConfiguration { | ||
convenience init(fromUapiConfig uapiConfig: String, basedOn base: TunnelConfiguration? = nil) throws { | ||
var interfaceConfiguration: InterfaceConfiguration? | ||
var peerConfigurations = [PeerConfiguration]() | ||
|
||
var lines = uapiConfig.split(separator: "\n") | ||
lines.append("") | ||
|
||
var parserState = ConfigurationParserState.inInterfaceSection | ||
var attributes = [String: String]() | ||
|
||
for line in lines { | ||
var key = "" | ||
var value = "" | ||
|
||
if !line.isEmpty { | ||
guard let equalsIndex = line.firstIndex(of: "=") else { | ||
throw ConfigurationParseError.invalidLine(line) | ||
} | ||
key = String(line[..<equalsIndex]) | ||
value = String(line[line.index(equalsIndex, offsetBy: 1)...]) | ||
} | ||
|
||
if line.isEmpty || key == "public_key" { | ||
// Previous section has ended; process the attributes collected so far | ||
if parserState == .inInterfaceSection { | ||
let interface = try TunnelConfiguration.collate(interfaceAttributes: attributes) | ||
guard interfaceConfiguration == nil else { throw ConfigurationParseError.multipleInterfaces } | ||
interfaceConfiguration = interface | ||
parserState = .inPeerSection | ||
} else if parserState == .inPeerSection { | ||
let peer = try TunnelConfiguration.collate(peerAttributes: attributes) | ||
peerConfigurations.append(peer) | ||
} | ||
attributes.removeAll() | ||
if line.isEmpty { | ||
break | ||
} | ||
} | ||
|
||
if let presentValue = attributes[key] { | ||
if key == "allowed_ip" { | ||
attributes[key] = presentValue + "," + value | ||
} else { | ||
throw ConfigurationParseError.multipleEntriesForKey(key) | ||
} | ||
} else { | ||
attributes[key] = value | ||
} | ||
|
||
if parserState == .inInterfaceSection { | ||
guard constants.interfaceSectionKeys.contains(key) else { | ||
throw ConfigurationParseError.interfaceHasUnrecognizedKey(key) | ||
} | ||
} | ||
if parserState == .inPeerSection { | ||
guard constants.peerSectionKeys.contains(key) else { | ||
throw ConfigurationParseError.peerHasUnrecognizedKey(key) | ||
} | ||
} | ||
} | ||
|
||
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey } | ||
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray) | ||
if peerPublicKeysArray.count != peerPublicKeysSet.count { | ||
throw ConfigurationParseError.multiplePeersWithSamePublicKey | ||
} | ||
|
||
interfaceConfiguration?.addresses = base?.interface.addresses ?? [] | ||
interfaceConfiguration?.dns = base?.interface.dns ?? [] | ||
interfaceConfiguration?.dnsSearch = base?.interface.dnsSearch ?? [] | ||
interfaceConfiguration?.mtu = base?.interface.mtu | ||
|
||
if let interfaceConfiguration = interfaceConfiguration { | ||
self.init(name: base?.name, interface: interfaceConfiguration, peers: peerConfigurations) | ||
} else { | ||
throw ConfigurationParseError.noInterface | ||
} | ||
} | ||
} | ||
|
||
private extension TunnelConfiguration { | ||
static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { | ||
guard let privateKeyString = attributes["private_key"] else { | ||
throw ConfigurationParseError.interfaceHasNoPrivateKey | ||
} | ||
guard let privateKey = PrivateKey(hexKey: privateKeyString) else { | ||
throw ConfigurationParseError.interfaceHasInvalidPrivateKey(privateKeyString) | ||
} | ||
var interface = InterfaceConfiguration(privateKey: privateKey) | ||
if let listenPortString = attributes["listen_port"] { | ||
guard let listenPort = UInt16(listenPortString) else { | ||
throw ConfigurationParseError.interfaceHasInvalidListenPort(listenPortString) | ||
} | ||
if listenPort != 0 { | ||
interface.listenPort = listenPort | ||
} | ||
} | ||
return interface | ||
} | ||
|
||
static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { | ||
guard let publicKeyString = attributes["public_key"] else { | ||
throw ConfigurationParseError.peerHasNoPublicKey | ||
} | ||
guard let publicKey = PublicKey(hexKey: publicKeyString) else { | ||
throw ConfigurationParseError.peerHasInvalidPublicKey(publicKeyString) | ||
} | ||
var peer = PeerConfiguration(publicKey: publicKey) | ||
if let preSharedKeyString = attributes["preshared_key"] { | ||
guard let preSharedKey = PreSharedKey(hexKey: preSharedKeyString) else { | ||
throw ConfigurationParseError.peerHasInvalidPreSharedKey(preSharedKeyString) | ||
} | ||
// TODO(zx2c4): does the compiler optimize this away? | ||
var accumulator: UInt8 = 0 | ||
for index in 0..<preSharedKey.rawValue.count { | ||
accumulator |= preSharedKey.rawValue[index] | ||
} | ||
if accumulator != 0 { | ||
peer.preSharedKey = preSharedKey | ||
} | ||
} | ||
if let allowedIPsString = attributes["allowed_ip"] { | ||
var allowedIPs = [IPAddressRange]() | ||
for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { | ||
guard let allowedIP = IPAddressRange(from: allowedIPString) else { | ||
throw ConfigurationParseError.peerHasInvalidAllowedIP(allowedIPString) | ||
} | ||
allowedIPs.append(allowedIP) | ||
} | ||
peer.allowedIPs = allowedIPs | ||
} | ||
if let endpointString = attributes["endpoint"] { | ||
guard let endpoint = Endpoint(from: endpointString) else { | ||
throw ConfigurationParseError.peerHasInvalidEndpoint(endpointString) | ||
} | ||
peer.endpoint = endpoint | ||
} | ||
if let persistentKeepAliveString = attributes["persistent_keepalive_interval"] { | ||
guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { | ||
throw ConfigurationParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) | ||
} | ||
if persistentKeepAlive != 0 { | ||
peer.persistentKeepAlive = persistentKeepAlive | ||
} | ||
} | ||
if let rxBytesString = attributes["rx_bytes"] { | ||
guard let rxBytes = UInt64(rxBytesString) else { | ||
throw ConfigurationParseError.peerHasInvalidTransferBytes(rxBytesString) | ||
} | ||
if rxBytes != 0 { | ||
peer.rxBytes = rxBytes | ||
} | ||
} | ||
if let txBytesString = attributes["tx_bytes"] { | ||
guard let txBytes = UInt64(txBytesString) else { | ||
throw ConfigurationParseError.peerHasInvalidTransferBytes(txBytesString) | ||
} | ||
if txBytes != 0 { | ||
peer.txBytes = txBytes | ||
} | ||
} | ||
if let lastHandshakeTimeSecString = attributes["last_handshake_time_sec"] { | ||
var lastHandshakeTimeSince1970: TimeInterval = 0 | ||
guard let lastHandshakeTimeSec = UInt64(lastHandshakeTimeSecString) else { | ||
throw ConfigurationParseError.peerHasInvalidLastHandshakeTime(lastHandshakeTimeSecString) | ||
} | ||
if lastHandshakeTimeSec != 0 { | ||
lastHandshakeTimeSince1970 += Double(lastHandshakeTimeSec) | ||
if let lastHandshakeTimeNsecString = attributes["last_handshake_time_nsec"] { | ||
guard let lastHandshakeTimeNsec = UInt64(lastHandshakeTimeNsecString) else { | ||
throw ConfigurationParseError.peerHasInvalidLastHandshakeTime(lastHandshakeTimeNsecString) | ||
} | ||
lastHandshakeTimeSince1970 += Double(lastHandshakeTimeNsec) / 1000000000.0 | ||
} | ||
peer.lastHandshakeTime = Date(timeIntervalSince1970: lastHandshakeTimeSince1970) | ||
} | ||
} | ||
return peer | ||
} | ||
} |
Oops, something went wrong.