]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
Tunnel: support getting runtime configuration
authorJason A. Donenfeld <Jason@zx2c4.com>
Wed, 23 Jan 2019 23:00:46 +0000 (00:00 +0100)
committerJason A. Donenfeld <Jason@zx2c4.com>
Thu, 24 Jan 2019 00:37:57 +0000 (01:37 +0100)
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
WireGuard/Shared/Model/PeerConfiguration.swift
WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/Base.lproj/Localizable.strings
WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift [new file with mode: 0644]
WireGuard/WireGuard/Tunnel/TunnelsManager.swift
WireGuard/WireGuard/UI/TunnelViewModel.swift
WireGuard/WireGuard/UI/macOS/ParseError+WireGuardAppError.swift
WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift

index 5271aa61ad4444e1e6c2aaf5c6cce6011e2e77e3..7fd3f87e856e0226889f56829f3ca3dccf688859 100644 (file)
@@ -17,6 +17,9 @@ struct PeerConfiguration {
     var allowedIPs = [IPAddressRange]()
     var endpoint: Endpoint?
     var persistentKeepAlive: UInt16?
+    var rxBytes: UInt64?
+    var txBytes: UInt64?
+    var lastHandshakeTime: Date?
 
     init(publicKey: Data) {
         self.publicKey = publicKey
index 625c25d408616ae7e20ba409aa02235ef7571bff..043914a432f46bd343d73f78c73516ff6f2e010d 100644 (file)
@@ -28,6 +28,8 @@ extension TunnelConfiguration {
         case peerHasInvalidAllowedIP(String)
         case peerHasInvalidEndpoint(String)
         case peerHasInvalidPersistentKeepAlive(String)
+        case peerHasInvalidTransferBytes(String)
+        case peerHasInvalidLastHandshakeTime(String)
         case peerHasUnrecognizedKey(String)
         case multiplePeersWithSamePublicKey
         case multipleEntriesForKey(String)
index 18f09354978a61032558765f4f77f75b7116a19b..1d8001787e2dc505f8bc9e4402fcd792e414fc39 100644 (file)
@@ -30,6 +30,8 @@
                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 */; };
+               6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
+               6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
                6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
                6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
                6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
                5F9696AF21CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+WgQuickConfig.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>"; };
+               6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
                6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
                6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
                6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; };
                        isa = PBXGroup;
                        children = (
                                6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
+                               6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */,
                                6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
                                5F4541A821C451D100994C13 /* TunnelStatus.swift */,
                                6FB1017821C57DE600766195 /* MockTunnels.swift */,
                                6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */,
                                6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */,
                                6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */,
+                               6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */,
                                6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
                                6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
                                6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
                                6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */,
                                5F45419021C2D53800994C13 /* SwitchCell.swift in Sources */,
                                6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */,
+                               6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */,
                                6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
                                5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
                                5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
index 0b2a33d020520b998dd524b19c6e93f86a9ed37c..2133331f8ca0e995bc55dfd71c745ab879b2c1d6 100644 (file)
@@ -62,6 +62,9 @@
 "tunnelPeerEndpoint" = "Endpoint";
 "tunnelPeerPersistentKeepalive" = "Persistent keepalive";
 "tunnelPeerAllowedIPs" = "Allowed IPs";
+"tunnelPeerRxBytes" = "Data received";
+"tunnelPeerTxBytes" = "Data sent";
+"tunnelPeerLastHandshakeTime" = "Latest handshake";
 "tunnelPeerExcludePrivateIPs" = "Exclude private IPs";
 
 "tunnelSectionTitleOnDemand" = "On-Demand Activation";
diff --git a/WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift b/WireGuard/WireGuard/Tunnel/TunnelConfiguration+UapiConfig.swift
new file mode 100644 (file)
index 0000000..63a8570
--- /dev/null
@@ -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
+            }
+        }
+    }
+}
index 75d823048779243cabc135f98e0329da516a7b5f..5e6ad5c1d089dd4b115dab59e2aadc8399995e41 100644 (file)
@@ -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
index 07d1bac168815472916a4706baa24e1e23cff498..9124a0040a6838ae748ea1b31e1b239cb3b6ea30 100644 (file)
@@ -40,6 +40,9 @@ class TunnelViewModel {
         case endpoint
         case persistentKeepAlive
         case allowedIPs
+        case rxBytes
+        case txBytes
+        case lastHandshakeTime
         case excludePrivateIPs
         case deletePeer
 
@@ -50,6 +53,9 @@ class TunnelViewModel {
             case .endpoint: return tr("tunnelPeerEndpoint")
             case .persistentKeepAlive: return tr("tunnelPeerPersistentKeepalive")
             case .allowedIPs: return tr("tunnelPeerAllowedIPs")
+            case .rxBytes: return tr("tunnelPeerRxBytes")
+            case .txBytes: return tr("tunnelPeerTxBytes")
+            case .lastHandshakeTime: return tr("tunnelPeerLastHandshakeTime")
             case .excludePrivateIPs: return tr("tunnelPeerExcludePrivateIPs")
             case .deletePeer: return tr("deletePeerButtonTitle")
             }
@@ -248,6 +254,18 @@ class TunnelViewModel {
             if let persistentKeepAlive = config.persistentKeepAlive {
                 scratchpad[.persistentKeepAlive] = String(persistentKeepAlive)
             }
+            // TODO(roopc): These next 3 fields should be prettier
+            // - bytes() in https://git.zx2c4.com/WireGuard/tree/src/tools/show.c#n185
+            // - ago() in https://git.zx2c4.com/WireGuard/tree/src/tools/show.c#n158
+            if let rxBytes = config.rxBytes {
+                scratchpad[.rxBytes] = String(rxBytes)
+            }
+            if let txBytes = config.txBytes {
+                scratchpad[.txBytes] = String(txBytes)
+            }
+            if let lastHandshakeTime = config.lastHandshakeTime {
+                scratchpad[.lastHandshakeTime] = lastHandshakeTime.description
+            }
             updateExcludePrivateIPsFieldState()
         }
 
index 9f2c009011837d561810487aae45c38937b8fbf4..6afbadb8bb60c45f98689a15e5e08deaff7d0aac 100644 (file)
@@ -43,6 +43,10 @@ extension TunnelConfiguration.ParseError: WireGuardAppError {
             return (tr(format: "macAlertPersistentKeepliveInvalid (%@)", value), tr("alertInvalidPeerMessagePersistentKeepaliveInvalid"))
         case .peerHasUnrecognizedKey(let value):
             return (tr(format: "macAlertUnrecognizedPeerKey (%@)", value), tr("macAlertInfoUnrecognizedPeerKey"))
+        case .peerHasInvalidTransferBytes(let line):
+            return (tr(format: "macAlertInvalidLine (%@)", String(line)), "")
+        case .peerHasInvalidLastHandshakeTime(let line):
+            return (tr(format: "macAlertInvalidLine (%@)", String(line)), "")
         case .multiplePeersWithSamePublicKey:
             return (tr("alertInvalidPeerMessagePublicKeyDuplicated"), "")
         case .multipleEntriesForKey(let value):
index 7c84491af2fd6595ae06eb809a5ed8edb2838214..5ac6b282096d5b0a21aa57f21b99a93269967549 100644 (file)
@@ -97,6 +97,24 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
         completionHandler()
     }
 
+    override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
+        guard let completionHandler = completionHandler else { return }
+        guard let handle = handle else {
+            completionHandler(nil)
+            return
+        }
+        if messageData.count == 1 && messageData[0] == 0 {
+            guard let settings = wgGetConfig(handle) else {
+                completionHandler(nil)
+                return
+            }
+            completionHandler(String(cString: settings).data(using: .utf8)!)
+            free(settings)
+        } else {
+            completionHandler(nil)
+        }
+    }
+
     private func configureLogger() {
         Logger.configureGlobal(withFilePath: FileManager.networkExtensionLogFileURL?.path)
         wgSetLogger { level, msgC in