From 3e449724bfdfefb73672162c02f0e1651514189b Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 30 Jan 2025 14:08:12 +1100 Subject: [PATCH] chore: pass session token to network extension --- .gitignore | 4 + Coder Desktop/.swiftformat | 3 + .../Coder Desktop/Coder_DesktopApp.swift | 8 +- Coder Desktop/Coder Desktop/State.swift | 4 +- Coder Desktop/VPN/Manager.swift | 27 ++- Coder Desktop/VPN/PacketTunnelProvider.swift | 79 +++++++- Coder Desktop/VPNLib/Convert.swift | 85 ++++----- Coder Desktop/VPNLib/Receiver.swift | 8 +- Coder Desktop/VPNLib/Speaker.swift | 6 +- Coder Desktop/VPNLib/Util.swift | 15 +- Coder Desktop/VPNLibTests/ConvertTests.swift | 175 +++++++++--------- Makefile | 1 - 12 files changed, 256 insertions(+), 159 deletions(-) create mode 100644 Coder Desktop/.swiftformat diff --git a/.gitignore b/.gitignore index 6d52644..51965fb 100644 --- a/.gitignore +++ b/.gitignore @@ -290,4 +290,8 @@ xcuserdata /*.gcno **/xcshareddata/WorkspaceSettings.xcsettings +### VSCode & Sweetpad ### +.vscode/** +buildServer.json + # End of https://www.toptal.com/developers/gitignore/api/xcode,jetbrains,macos,direnv,swift,swiftpm,objective-c diff --git a/Coder Desktop/.swiftformat b/Coder Desktop/.swiftformat new file mode 100644 index 0000000..cb200b4 --- /dev/null +++ b/Coder Desktop/.swiftformat @@ -0,0 +1,3 @@ +--selfrequired log,info,error,debug,critical,fault +--exclude **.pb.swift +--condassignment always \ No newline at end of file diff --git a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift index bfb01ce..4bec8d2 100644 --- a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift +++ b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift @@ -16,9 +16,10 @@ struct DesktopApp: App { .environmentObject(appDelegate.settings) } .windowResizability(.contentSize) - SwiftUI.Settings { SettingsView() - .environmentObject(appDelegate.vpn) - .environmentObject(appDelegate.settings) + SwiftUI.Settings { + SettingsView() + .environmentObject(appDelegate.vpn) + .environmentObject(appDelegate.settings) } .windowResizability(.contentSize) } @@ -32,7 +33,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { let settings: Settings override init() { - // TODO: Replace with real implementation vpn = CoderVPNService() settings = Settings() session = SecureSession(onChange: vpn.configureTunnelProviderProtocol) diff --git a/Coder Desktop/Coder Desktop/State.swift b/Coder Desktop/Coder Desktop/State.swift index 01ef7c2..cb51450 100644 --- a/Coder Desktop/Coder Desktop/State.swift +++ b/Coder Desktop/Coder Desktop/State.swift @@ -41,7 +41,9 @@ class SecureSession: ObservableObject, Session { if !hasSession { return nil } let proto = NETunnelProviderProtocol() proto.providerBundleIdentifier = "\(appId).VPN" - proto.passwordReference = keychain[attributes: Keys.sessionToken]?.persistentRef + // HACK: We can't write to the system keychain, and the user keychain + // isn't accessible, so we'll use providerConfiguration, which is over XPC. + proto.providerConfiguration = ["token": sessionToken!] proto.serverAddress = baseAccessURL!.absoluteString return proto } diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index 3ca0004..9a3e35c 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -79,11 +79,11 @@ actor Manager { case let .message(msg): handleMessage(msg) case let .RPC(rpc): - handleRPC(rpc) + await handleRPC(rpc) } } } catch { - logger.error("tunnel read loop failed: \(error)") + logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)") try await tunnelHandle.close() // TODO: Notify app over XPC return @@ -108,21 +108,33 @@ actor Manager { } } - func handleRPC(_ rpc: RPCRequest) { + func handleRPC(_ rpc: RPCRequest) async { guard let msgType = rpc.msg.msg else { logger.critical("received rpc with no type") return } switch msgType { case let .networkSettings(ns): - let neSettings = convertNetworkSettingsRequest(ns) - ptp.setTunnelNetworkSettings(neSettings) + do { + try await ptp.applyTunnelNetworkSettings(ns) + try? await rpc.sendReply(.with { resp in + resp.networkSettings = .with { settings in + settings.success = true + } + }) + } catch { + try? await rpc.sendReply(.with { resp in + resp.networkSettings = .with { settings in + settings.success = false + settings.errorMessage = error.localizedDescription + } + }) + } case .log, .peerUpdate, .start, .stop: logger.critical("received unexpected rpc: `\(String(describing: msgType))`") } } - // TODO: Call via XPC func startVPN() async throws(ManagerError) { logger.info("sending start rpc") guard let tunFd = ptp.tunnelFileDescriptor else { @@ -149,7 +161,6 @@ actor Manager { // TODO: notify app over XPC } - // TODO: Call via XPC func stopVPN() async throws(ManagerError) { logger.info("sending stop rpc") let resp: Vpn_TunnelMessage @@ -246,5 +257,5 @@ func writeVpnLog(_ log: Vpn_Log) { category: log.loggerNames.joined(separator: ".") ) let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ") - logger.log(level: level, "\(log.message): \(fields)") + logger.log(level: level, "\(log.message, privacy: .public): \(fields, privacy: .public)") } diff --git a/Coder Desktop/VPN/PacketTunnelProvider.swift b/Coder Desktop/VPN/PacketTunnelProvider.swift index 308882c..e548d8c 100644 --- a/Coder Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder Desktop/VPN/PacketTunnelProvider.swift @@ -8,6 +8,8 @@ let CTLIOCGINFO: UInt = 0xC064_4E03 class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "provider") private var manager: Manager? + // a `tunnelRemoteAddress` is required, but not currently used. + private var currentSettings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "127.0.0.1") var tunnelFileDescriptor: Int32? { var ctlInfo = ctl_info() @@ -41,21 +43,42 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { return nil } - override func startTunnel(options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { + override func startTunnel( + options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void + ) { logger.debug("startTunnel called") guard manager == nil else { logger.error("startTunnel called with non-nil Manager") - completionHandler(nil) + completionHandler(PTPError.alreadyRunning) return } + guard let proto = protocolConfiguration as? NETunnelProviderProtocol, + let baseAccessURL = proto.serverAddress + else { + logger.error("startTunnel called with nil protocolConfiguration") + completionHandler(PTPError.missingConfiguration) + return + } + // HACK: We can't write to the system keychain, and the NE can't read the user keychain. + guard let token = proto.providerConfiguration?["token"] as? String else { + logger.error("startTunnel called with nil token") + completionHandler(PTPError.missingToken) + return + } + logger.debug("retrieved token & access URL") let completionHandler = CallbackWrapper(completionHandler) Task { - // TODO: Retrieve access URL & Token via Keychain do throws(ManagerError) { + logger.debug("creating manager") manager = try await Manager( with: self, - cfg: .init(apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!) + cfg: .init( + apiToken: token, serverUrl: .init(string: baseAccessURL)! + ) ) + logger.debug("starting vpn") + try await manager!.startVPN() + logger.info("vpn started") completionHandler(nil) } catch { completionHandler(error) @@ -64,15 +87,26 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { } } - override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) { + override func stopTunnel( + with _: NEProviderStopReason, completionHandler: @escaping () -> Void + ) { logger.debug("stopTunnel called") - guard manager != nil else { + guard let manager else { logger.error("stopTunnel called with nil Manager") completionHandler() return } - manager = nil - completionHandler() + + let completionHandler = CompletionWrapper(completionHandler) + Task { [manager] in + do throws(ManagerError) { + try await manager.stopVPN() + } catch { + logger.error("error stopping manager: \(error.description, privacy: .public)") + } + completionHandler() + } + self.manager = nil } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { @@ -92,4 +126,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { // Add code here to wake up. logger.debug("wake called") } + + // Wrapper around `setTunnelNetworkSettings` that supports merging updates + func applyTunnelNetworkSettings(_ diff: Vpn_NetworkSettingsRequest) async throws { + logger.debug("applying settings diff: \(diff.debugDescription, privacy: .public)") + + if diff.hasDnsSettings { + currentSettings.dnsSettings = convertDnsSettings(diff.dnsSettings) + } + + if diff.mtu != 0 { + currentSettings.mtu = NSNumber(value: diff.mtu) + } + + if diff.hasIpv4Settings { + currentSettings.ipv4Settings = convertIPv4Settings(diff.ipv4Settings) + } + if diff.hasIpv6Settings { + currentSettings.ipv6Settings = convertIPv6Settings(diff.ipv6Settings) + } + + logger.info("applying settings: \(self.currentSettings.debugDescription, privacy: .public)") + try await setTunnelNetworkSettings(currentSettings) + } +} + +enum PTPError: Error { + case alreadyRunning + case missingConfiguration + case missingToken } diff --git a/Coder Desktop/VPNLib/Convert.swift b/Coder Desktop/VPNLib/Convert.swift index c3e9401..6784693 100644 --- a/Coder Desktop/VPNLib/Convert.swift +++ b/Coder Desktop/VPNLib/Convert.swift @@ -1,60 +1,61 @@ import NetworkExtension import os -// swiftlint:disable:next function_body_length -public func convertNetworkSettingsRequest(_ req: Vpn_NetworkSettingsRequest) -> NEPacketTunnelNetworkSettings { - let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: req.tunnelRemoteAddress) - networkSettings.tunnelOverheadBytes = NSNumber(value: req.tunnelOverheadBytes) - networkSettings.mtu = NSNumber(value: req.mtu) +public func convertDnsSettings(_ req: Vpn_NetworkSettingsRequest.DNSSettings) -> NEDNSSettings { + let dnsSettings = NEDNSSettings(servers: req.servers) + dnsSettings.searchDomains = req.searchDomains + dnsSettings.domainName = req.domainName + dnsSettings.matchDomains = req.matchDomains + dnsSettings.matchDomainsNoSearch = req.matchDomainsNoSearch + return dnsSettings +} - if req.hasDnsSettings { - let dnsSettings = NEDNSSettings(servers: req.dnsSettings.servers) - dnsSettings.searchDomains = req.dnsSettings.searchDomains - dnsSettings.domainName = req.dnsSettings.domainName - dnsSettings.matchDomains = req.dnsSettings.matchDomains - dnsSettings.matchDomainsNoSearch = req.dnsSettings.matchDomainsNoSearch - networkSettings.dnsSettings = dnsSettings +public func convertIPv4Settings(_ req: Vpn_NetworkSettingsRequest.IPv4Settings) -> NEIPv4Settings { + let ipv4Settings = NEIPv4Settings(addresses: req.addrs, subnetMasks: req.subnetMasks) + if !req.router.isEmpty { + ipv4Settings.router = req.router } - - if req.hasIpv4Settings { - let ipv4Settings = NEIPv4Settings(addresses: req.ipv4Settings.addrs, subnetMasks: req.ipv4Settings.subnetMasks) - ipv4Settings.router = req.ipv4Settings.router - ipv4Settings.includedRoutes = req.ipv4Settings.includedRoutes.map { - let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask) + ipv4Settings.includedRoutes = req.includedRoutes.map { + let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask) + if !$0.router.isEmpty { route.gatewayAddress = $0.router - return route } - ipv4Settings.excludedRoutes = req.ipv4Settings.excludedRoutes.map { - let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask) + return route + } + ipv4Settings.excludedRoutes = req.excludedRoutes.map { + let route = NEIPv4Route(destinationAddress: $0.destination, subnetMask: $0.mask) + if !$0.router.isEmpty { route.gatewayAddress = $0.router - return route } - networkSettings.ipv4Settings = ipv4Settings + return route } + return ipv4Settings +} - if req.hasIpv6Settings { - let ipv6Settings = NEIPv6Settings( - addresses: req.ipv6Settings.addrs, - networkPrefixLengths: req.ipv6Settings.prefixLengths.map { NSNumber(value: $0) - } +public func convertIPv6Settings(_ req: Vpn_NetworkSettingsRequest.IPv6Settings) -> NEIPv6Settings { + let ipv6Settings = NEIPv6Settings( + addresses: req.addrs, + networkPrefixLengths: req.prefixLengths.map { NSNumber(value: $0) } + ) + ipv6Settings.includedRoutes = req.includedRoutes.map { + let route = NEIPv6Route( + destinationAddress: $0.destination, + networkPrefixLength: NSNumber(value: $0.prefixLength) ) - ipv6Settings.includedRoutes = req.ipv6Settings.includedRoutes.map { - let route = NEIPv6Route( - destinationAddress: $0.destination, - networkPrefixLength: NSNumber(value: $0.prefixLength) - ) + if !$0.router.isEmpty { route.gatewayAddress = $0.router - return route } - ipv6Settings.excludedRoutes = req.ipv6Settings.excludedRoutes.map { - let route = NEIPv6Route( - destinationAddress: $0.destination, - networkPrefixLength: NSNumber(value: $0.prefixLength) - ) + return route + } + ipv6Settings.excludedRoutes = req.excludedRoutes.map { + let route = NEIPv6Route( + destinationAddress: $0.destination, + networkPrefixLength: NSNumber(value: $0.prefixLength) + ) + if !$0.router.isEmpty { route.gatewayAddress = $0.router - return route } - networkSettings.ipv6Settings = ipv6Settings + return route } - return networkSettings + return ipv6Settings } diff --git a/Coder Desktop/VPNLib/Receiver.swift b/Coder Desktop/VPNLib/Receiver.swift index b139283..8151c3c 100644 --- a/Coder Desktop/VPNLib/Receiver.swift +++ b/Coder Desktop/VPNLib/Receiver.swift @@ -6,7 +6,6 @@ import SwiftProtobuf actor Receiver { private let dispatch: DispatchIO private let queue: DispatchQueue - private var running = false private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "proto") /// Creates an instance using the given `DispatchIO` channel and queue. @@ -58,11 +57,7 @@ actor Receiver { /// Starts reading protocol messages from the `DispatchIO` channel and returns them as an `AsyncStream` of messages. /// On read or decoding error, it logs and closes the stream. func messages() throws(ReceiveError) -> AsyncStream { - if running { - throw .alreadyRunning - } - running = true - return AsyncStream( + AsyncStream( unfolding: { do { let length = try await self.readLen() @@ -83,7 +78,6 @@ actor Receiver { enum ReceiveError: Error { case readError(String) case invalidLength - case alreadyRunning } func deserializeLen(_ data: Data) throws -> UInt32 { diff --git a/Coder Desktop/VPNLib/Speaker.swift b/Coder Desktop/VPNLib/Speaker.swift index 384a8a7..27dbf2b 100644 --- a/Coder Desktop/VPNLib/Speaker.swift +++ b/Coder Desktop/VPNLib/Speaker.swift @@ -79,10 +79,10 @@ public actor Speaker: @unchecked Sendable { +public struct CallbackWrapper: @unchecked Sendable { private let block: (T?) -> U public init(_ block: @escaping (T?) -> U) { @@ -6,7 +6,18 @@ public final class CallbackWrapper: @unchecked Sendable { } public func callAsFunction(_ error: T?) -> U { - // Just forward to the original block block(error) } } + +public struct CompletionWrapper: @unchecked Sendable { + private let block: () -> T + + public init(_ block: @escaping () -> T) { + self.block = block + } + + public func callAsFunction() -> T { + block() + } +} diff --git a/Coder Desktop/VPNLibTests/ConvertTests.swift b/Coder Desktop/VPNLibTests/ConvertTests.swift index b61a28b..9bf35f4 100644 --- a/Coder Desktop/VPNLibTests/ConvertTests.swift +++ b/Coder Desktop/VPNLibTests/ConvertTests.swift @@ -2,98 +2,107 @@ import Testing @testable import VPNLib @Suite(.timeLimit(.minutes(1))) -struct ConvertTests { +struct ConvertNetworkSettingsTests { @Test - // swiftlint:disable:next function_body_length - func convertProtoNetworkSettingsRequest() async throws { - let req: Vpn_NetworkSettingsRequest = .with { req in - req.tunnelRemoteAddress = "10.0.0.1" - req.tunnelOverheadBytes = 20 - req.mtu = 1400 + func testConvertDnsSettings() async throws { + let req: Vpn_NetworkSettingsRequest.DNSSettings = .with { dns in + dns.servers = ["8.8.8.8"] + dns.searchDomains = ["example.com"] + dns.domainName = "example.com" + dns.matchDomains = ["example.com"] + dns.matchDomainsNoSearch = false + } - req.dnsSettings = .with { dns in - dns.servers = ["8.8.8.8"] - dns.searchDomains = ["example.com"] - dns.domainName = "example.com" - dns.matchDomains = ["example.com"] - dns.matchDomainsNoSearch = false - } + let result = convertDnsSettings(req) - req.ipv4Settings = .with { ipv4 in - ipv4.addrs = ["192.168.1.1"] - ipv4.subnetMasks = ["255.255.255.0"] - ipv4.router = "192.168.1.254" - ipv4.includedRoutes = [ - .with { route in - route.destination = "10.0.0.0" - route.mask = "255.0.0.0" - route.router = "192.168.1.254" - }, - ] - ipv4.excludedRoutes = [ - .with { route in - route.destination = "172.16.0.0" - route.mask = "255.240.0.0" - route.router = "192.168.1.254" - }, - ] - } + #expect(result.servers == req.servers) + #expect(result.searchDomains == req.searchDomains) + #expect(result.domainName == req.domainName) + #expect(result.matchDomains == req.matchDomains) + #expect(result.matchDomainsNoSearch == req.matchDomainsNoSearch) + } - req.ipv6Settings = .with { ipv6 in - ipv6.addrs = ["2001:db8::1"] - ipv6.prefixLengths = [64] - ipv6.includedRoutes = [ - .with { route in - route.destination = "2001:db8::" - route.router = "2001:db8::1" - route.prefixLength = 64 - }, - ] - ipv6.excludedRoutes = [ - .with { route in - route.destination = "2001:0db8:85a3::" - route.router = "2001:db8::1" - route.prefixLength = 128 - }, - ] - } + @Test + func testConvertIPv4Settings() async throws { + let req: Vpn_NetworkSettingsRequest.IPv4Settings = .with { ipv4 in + ipv4.addrs = ["192.168.1.1"] + ipv4.subnetMasks = ["255.255.255.0"] + ipv4.router = "192.168.1.254" + ipv4.includedRoutes = [ + .with { route in + route.destination = "10.0.0.0" + route.mask = "255.0.0.0" + route.router = "192.168.1.254" + }, + ] + ipv4.excludedRoutes = [ + .with { route in + route.destination = "172.16.0.0" + route.mask = "255.240.0.0" + route.router = "192.168.1.254" + }, + ] } - let result = convertNetworkSettingsRequest(req) - #expect(result.tunnelRemoteAddress == req.tunnelRemoteAddress) - #expect(result.dnsSettings!.servers == req.dnsSettings.servers) - #expect(result.dnsSettings!.domainName == req.dnsSettings.domainName) - #expect(result.ipv4Settings!.addresses == req.ipv4Settings.addrs) - #expect(result.ipv4Settings!.subnetMasks == req.ipv4Settings.subnetMasks) - #expect(result.ipv6Settings!.addresses == req.ipv6Settings.addrs) - #expect(result.ipv6Settings!.networkPrefixLengths == [64]) + let result = convertIPv4Settings(req) + + #expect(result.addresses == req.addrs) + #expect(result.subnetMasks == req.subnetMasks) + #expect(result.router == req.router) + + try #require(result.includedRoutes?.count == req.includedRoutes.count) + let includedRoute = result.includedRoutes![0] + let expectedIncludedRoute = req.includedRoutes[0] + #expect(includedRoute.destinationAddress == expectedIncludedRoute.destination) + #expect(includedRoute.destinationSubnetMask == expectedIncludedRoute.mask) + #expect(includedRoute.gatewayAddress == expectedIncludedRoute.router) + + try #require(result.excludedRoutes?.count == req.excludedRoutes.count) + let excludedRoute = result.excludedRoutes![0] + let expectedExcludedRoute = req.excludedRoutes[0] + #expect(excludedRoute.destinationAddress == expectedExcludedRoute.destination) + #expect(excludedRoute.destinationSubnetMask == expectedExcludedRoute.mask) + #expect(excludedRoute.gatewayAddress == expectedExcludedRoute.router) + } + + @Test + func testConvertIPv6Settings() async throws { + let req: Vpn_NetworkSettingsRequest.IPv6Settings = .with { ipv6 in + ipv6.addrs = ["2001:db8::1"] + ipv6.prefixLengths = [64] + ipv6.includedRoutes = [ + .with { route in + route.destination = "2001:db8::" + route.router = "2001:db8::1" + route.prefixLength = 64 + }, + ] + ipv6.excludedRoutes = [ + .with { route in + route.destination = "2001:0db8:85a3::" + route.router = "2001:db8::1" + route.prefixLength = 128 + }, + ] + } - try #require(result.ipv4Settings!.includedRoutes?.count == req.ipv4Settings.includedRoutes.count) - let ipv4IncludedRoute = result.ipv4Settings!.includedRoutes![0] - let expectedIpv4IncludedRoute = req.ipv4Settings.includedRoutes[0] - #expect(ipv4IncludedRoute.destinationAddress == expectedIpv4IncludedRoute.destination) - #expect(ipv4IncludedRoute.destinationSubnetMask == expectedIpv4IncludedRoute.mask) - #expect(ipv4IncludedRoute.gatewayAddress == expectedIpv4IncludedRoute.router) + let result = convertIPv6Settings(req) - try #require(result.ipv4Settings!.excludedRoutes?.count == req.ipv4Settings.excludedRoutes.count) - let ipv4ExcludedRoute = result.ipv4Settings!.excludedRoutes![0] - let expectedIpv4ExcludedRoute = req.ipv4Settings.excludedRoutes[0] - #expect(ipv4ExcludedRoute.destinationAddress == expectedIpv4ExcludedRoute.destination) - #expect(ipv4ExcludedRoute.destinationSubnetMask == expectedIpv4ExcludedRoute.mask) - #expect(ipv4ExcludedRoute.gatewayAddress == expectedIpv4ExcludedRoute.router) + #expect(result.addresses == req.addrs) + #expect(result.networkPrefixLengths == req.prefixLengths.map { NSNumber(value: $0) }) - try #require(result.ipv6Settings!.includedRoutes?.count == req.ipv6Settings.includedRoutes.count) - let ipv6IncludedRoute = result.ipv6Settings!.includedRoutes![0] - let expectedIpv6IncludedRoute = req.ipv6Settings.includedRoutes[0] - #expect(ipv6IncludedRoute.destinationAddress == expectedIpv6IncludedRoute.destination) - #expect(ipv6IncludedRoute.destinationNetworkPrefixLength == 64) - #expect(ipv6IncludedRoute.gatewayAddress == expectedIpv6IncludedRoute.router) + try #require(result.includedRoutes?.count == req.includedRoutes.count) + let includedRoute = result.includedRoutes![0] + let expectedIncludedRoute = req.includedRoutes[0] + #expect(includedRoute.destinationAddress == expectedIncludedRoute.destination) + #expect(includedRoute.destinationNetworkPrefixLength == NSNumber(value: 64)) + #expect(includedRoute.gatewayAddress == expectedIncludedRoute.router) - try #require(result.ipv6Settings!.excludedRoutes?.count == req.ipv6Settings.excludedRoutes.count) - let ipv6ExcludedRoute = result.ipv6Settings!.excludedRoutes![0] - let expectedIpv6ExcludedRoute = req.ipv6Settings.excludedRoutes[0] - #expect(ipv6ExcludedRoute.destinationAddress == expectedIpv6ExcludedRoute.destination) - #expect(ipv6ExcludedRoute.destinationNetworkPrefixLength == 128) - #expect(ipv6ExcludedRoute.gatewayAddress == expectedIpv6ExcludedRoute.router) + try #require(result.excludedRoutes?.count == req.excludedRoutes.count) + let excludedRoute = result.excludedRoutes![0] + let expectedExcludedRoute = req.excludedRoutes[0] + #expect(excludedRoute.destinationAddress == expectedExcludedRoute.destination) + #expect(excludedRoute.destinationNetworkPrefixLength == NSNumber(value: 128)) + #expect(excludedRoute.gatewayAddress == expectedExcludedRoute.router) } } diff --git a/Makefile b/Makefile index e66eb59..7e078de 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,6 @@ $(PROJECT)/VPNLib/vpn.pb.swift: $(PROJECT)/VPNLib/vpn.proto .PHONY: fmt fmt: ## Run Swift file formatter swiftformat \ - --exclude '**.pb.swift' \ --swiftversion $(SWIFT_VERSION) \ $(FMTFLAGS) .