diff options
Diffstat (limited to 'WireGuard/WireGuard/Tunnel')
-rw-r--r-- | WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift | 207 | ||||
-rw-r--r-- | WireGuard/WireGuard/Tunnel/TunnelsManager.swift | 17 |
2 files changed, 224 insertions, 0 deletions
diff --git a/WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift b/WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift new file mode 100644 index 0000000..63a8570 --- /dev/null +++ b/WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Foundation + +extension TunnelConfiguration { + //swiftlint:disable:next function_body_length cyclomatic_complexity + 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 = ParserState.inInterfaceSection + var attributes = [String: String]() + + for line in lines { + var key = "" + var value = "" + + if !line.isEmpty { + guard let equalsIndex = line.firstIndex(of: "=") else { throw ParseError.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 ParseError.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 ParseError.multipleEntriesForKey(key) + } + } else { + attributes[key] = value + } + + 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"] + + if parserState == .inInterfaceSection { + guard interfaceSectionKeys.contains(key) else { + throw ParseError.interfaceHasUnrecognizedKey(key) + } + } + if parserState == .inPeerSection { + guard peerSectionKeys.contains(key) else { + throw ParseError.peerHasUnrecognizedKey(key) + } + } + } + + let peerPublicKeysArray = peerConfigurations.map { $0.publicKey } + let peerPublicKeysSet = Set<Data>(peerPublicKeysArray) + if peerPublicKeysArray.count != peerPublicKeysSet.count { + throw ParseError.multiplePeersWithSamePublicKey + } + + interfaceConfiguration?.addresses = base?.interface.addresses ?? [] + interfaceConfiguration?.dns = base?.interface.dns ?? [] + interfaceConfiguration?.mtu = base?.interface.mtu + + if let interfaceConfiguration = interfaceConfiguration { + self.init(name: base?.name, interface: interfaceConfiguration, peers: peerConfigurations) + } else { + throw ParseError.noInterface + } + } + + private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { + guard let privateKeyString = attributes["private_key"] else { + throw ParseError.interfaceHasNoPrivateKey + } + guard let privateKey = Data(hexEncoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { + throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString) + } + var interface = InterfaceConfiguration(privateKey: privateKey) + if let listenPortString = attributes["listen_port"] { + guard let listenPort = UInt16(listenPortString) else { + throw ParseError.interfaceHasInvalidListenPort(listenPortString) + } + if listenPort != 0 { + interface.listenPort = listenPort + } + } + return interface + } + + //swiftlint:disable:next cyclomatic_complexity + private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { + guard let publicKeyString = attributes["public_key"] else { + throw ParseError.peerHasNoPublicKey + } + guard let publicKey = Data(hexEncoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { + throw ParseError.peerHasInvalidPublicKey(publicKeyString) + } + var peer = PeerConfiguration(publicKey: publicKey) + if let preSharedKeyString = attributes["preshared_key"] { + guard let preSharedKey = Data(hexEncoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else { + throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString) + } + // TODO(zx2c4): does the compiler optimize this away? + var accumulator: UInt8 = 0 + for index in 0..<preSharedKey.count { + accumulator |= preSharedKey[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 ParseError.peerHasInvalidAllowedIP(allowedIPString) + } + allowedIPs.append(allowedIP) + } + peer.allowedIPs = allowedIPs + } + if let endpointString = attributes["endpoint"] { + guard let endpoint = Endpoint(from: endpointString) else { + throw ParseError.peerHasInvalidEndpoint(endpointString) + } + peer.endpoint = endpoint + } + if let persistentKeepAliveString = attributes["persistent_keepalive_interval"] { + guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { + throw ParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) + } + if persistentKeepAlive != 0 { + peer.persistentKeepAlive = persistentKeepAlive + } + } + if let rxBytesString = attributes["rx_bytes"] { + guard let rxBytes = UInt64(rxBytesString) else { + throw ParseError.peerHasInvalidTransferBytes(rxBytesString) + } + if rxBytes != 0 { + peer.rxBytes = rxBytes + } + } + if let txBytesString = attributes["tx_bytes"] { + guard let txBytes = UInt64(txBytesString) else { + throw ParseError.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 ParseError.peerHasInvalidLastHandshakeTime(lastHandshakeTimeSecString) + } + if lastHandshakeTimeSec != 0 { + lastHandshakeTimeSince1970 += Double(lastHandshakeTimeSec) + if let lastHandshakeTimeNsecString = attributes["last_handshake_time_nsec"] { + guard let lastHandshakeTimeNsec = UInt64(lastHandshakeTimeNsecString) else { + throw ParseError.peerHasInvalidLastHandshakeTime(lastHandshakeTimeNsecString) + } + lastHandshakeTimeSince1970 += Double(lastHandshakeTimeNsec) / 1000000000.0 + } + peer.lastHandshakeTime = Date(timeIntervalSince1970: lastHandshakeTimeSince1970) + } + } + return peer + } +} + +extension Data { + //swiftlint:disable identifier_name + init?(hexEncoded hexString: String) { + if hexString.count % 2 != 0 { + return nil + } + let len = hexString.count / 2 + self.init(capacity: len) + for i in 0..<len { + let j = hexString.index(hexString.startIndex, offsetBy: i * 2) + let k = hexString.index(j, offsetBy: 2) + let bytes = hexString[j..<k] + if var num = UInt8(bytes, radix: 16) { + append(&num, count: 1) + } else { + return nil + } + } + } +} diff --git a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift index 75d8230..5e6ad5c 100644 --- a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift +++ b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift @@ -397,6 +397,23 @@ class TunnelContainer: NSObject { super.init() } + func getRuntimeTunnelConfiguration(completionHandler: @escaping ((TunnelConfiguration?) -> Void)) { + guard status != .inactive, let session = tunnelProvider.connection as? NETunnelProviderSession else { + completionHandler(tunnelConfiguration) + return + } + guard nil != (try? session.sendProviderMessage(Data(bytes: [ 0 ]), responseHandler: { + guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else { + completionHandler(self.tunnelConfiguration) + return + } + completionHandler((try? TunnelConfiguration(fromUapiConfig: settings, basedOn: base)) ?? self.tunnelConfiguration) + })) else { + completionHandler(tunnelConfiguration) + return + } + } + func refreshStatus() { let status = TunnelStatus(from: tunnelProvider.connection.status) self.status = status |