diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj index f65c5a1..b23a4a1 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC33E2D0061F200E1ABAA /* FluidMenuBarExtra */; }; AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = AA8BC4CE2D00A4B700E1ABAA /* KeychainAccess */; }; AAD720D02D0816B200F6304D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = AAD720CF2D0816B200F6304D /* Alamofire */; }; + 961679532CFF207900B2B6DF /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 961679522CFF207900B2B6DF /* SwiftProtobuf */; }; + 961679552CFF207900B2B6DF /* SwiftProtobufPluginLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 961679542CFF207900B2B6DF /* SwiftProtobufPluginLibrary */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,6 +39,13 @@ remoteGlobalIDString = 9616792F2CFF117300B2B6DF; remoteInfo = VPN; }; + 961679DD2D030E1D00B2B6DF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 961678F42CFF100D00B2B6DF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 961678FB2CFF100D00B2B6DF; + remoteInfo = "Coder Desktop"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -59,6 +68,7 @@ 961679192CFF100E00B2B6DF /* Coder DesktopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Coder DesktopUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = "com.coder.Coder-Desktop.VPN.systemextension"; sourceTree = BUILT_PRODUCTS_DIR; }; 961679322CFF117300B2B6DF /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ProtoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -69,6 +79,13 @@ ); target = 9616792F2CFF117300B2B6DF /* VPN */; }; + 961679472CFF14EA00B2B6DF /* Exceptions for "Proto" folder in "VPN" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Sender.swift, + ); + target = 9616792F2CFF117300B2B6DF /* VPN */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -95,6 +112,19 @@ path = VPN; sourceTree = ""; }; + 961679432CFF149000B2B6DF /* Proto */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 961679472CFF14EA00B2B6DF /* Exceptions for "Proto" folder in "VPN" target */, + ); + path = Proto; + sourceTree = ""; + }; + 961679DA2D030E1D00B2B6DF /* ProtoTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = ProtoTests; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -105,6 +135,8 @@ AAD720D02D0816B200F6304D /* Alamofire in Frameworks */, AA8BC4CF2D00A4B700E1ABAA /* KeychainAccess in Frameworks */, AA8BC33F2D0061F200E1ABAA /* FluidMenuBarExtra in Frameworks */, + 961679552CFF207900B2B6DF /* SwiftProtobufPluginLibrary in Frameworks */, + 961679532CFF207900B2B6DF /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -127,7 +159,16 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 961679E52D03144C00B2B6DF /* SwiftProtobufPluginLibrary in Frameworks */, 961679332CFF117300B2B6DF /* NetworkExtension.framework in Frameworks */, + 961679E32D03144900B2B6DF /* SwiftProtobuf in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 961679D62D030E1D00B2B6DF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -137,6 +178,8 @@ 961678F32CFF100D00B2B6DF = { isa = PBXGroup; children = ( + 961679432CFF149000B2B6DF /* Proto */, + 961679DA2D030E1D00B2B6DF /* ProtoTests */, 961678FE2CFF100D00B2B6DF /* Coder Desktop */, 961679122CFF100E00B2B6DF /* Coder DesktopTests */, 9616791C2CFF100E00B2B6DF /* Coder DesktopUITests */, @@ -153,6 +196,7 @@ 9616790F2CFF100E00B2B6DF /* Coder DesktopTests.xctest */, 961679192CFF100E00B2B6DF /* Coder DesktopUITests.xctest */, 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */, + 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */, ); name = Products; sourceTree = ""; @@ -185,12 +229,15 @@ ); fileSystemSynchronizedGroups = ( 961678FE2CFF100D00B2B6DF /* Coder Desktop */, + 961679432CFF149000B2B6DF /* Proto */, ); name = "Coder Desktop"; packageProductDependencies = ( AA8BC33E2D0061F200E1ABAA /* FluidMenuBarExtra */, AA8BC4CE2D00A4B700E1ABAA /* KeychainAccess */, AAD720CF2D0816B200F6304D /* Alamofire */, + 961679522CFF207900B2B6DF /* SwiftProtobuf */, + 961679542CFF207900B2B6DF /* SwiftProtobufPluginLibrary */, ); productName = "Coder Desktop"; productReference = 961678FC2CFF100D00B2B6DF /* Coder Desktop.app */; @@ -260,11 +307,36 @@ ); name = VPN; packageProductDependencies = ( + 961679E22D03144900B2B6DF /* SwiftProtobuf */, + 961679E42D03144C00B2B6DF /* SwiftProtobufPluginLibrary */, ); productName = VPN; productReference = 961679302CFF117300B2B6DF /* com.coder.Coder-Desktop.VPN.systemextension */; productType = "com.apple.product-type.system-extension"; }; + 961679D82D030E1D00B2B6DF /* ProtoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 961679DF2D030E1D00B2B6DF /* Build configuration list for PBXNativeTarget "ProtoTests" */; + buildPhases = ( + 961679D52D030E1D00B2B6DF /* Sources */, + 961679D62D030E1D00B2B6DF /* Frameworks */, + 961679D72D030E1D00B2B6DF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 961679DE2D030E1D00B2B6DF /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 961679DA2D030E1D00B2B6DF /* ProtoTests */, + ); + name = ProtoTests; + packageProductDependencies = ( + ); + productName = ProtoTests; + productReference = 961679D92D030E1D00B2B6DF /* ProtoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -289,6 +361,10 @@ 9616792F2CFF117300B2B6DF = { CreatedOnToolsVersion = 16.1; }; + 961679D82D030E1D00B2B6DF = { + CreatedOnToolsVersion = 16.1; + TestTargetID = 961678FB2CFF100D00B2B6DF; + }; }; }; buildConfigurationList = 961678F72CFF100D00B2B6DF /* Build configuration list for PBXProject "Coder Desktop" */; @@ -306,6 +382,7 @@ AA8BC33D2D0061F200E1ABAA /* XCRemoteSwiftPackageReference "fluid-menu-bar-extra" */, AA8BC4CD2D00A4B700E1ABAA /* XCRemoteSwiftPackageReference "KeychainAccess" */, AAD720CE2D0816B200F6304D /* XCRemoteSwiftPackageReference "Alamofire" */, + 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */, ); preferredProjectObjectVersion = 77; productRefGroup = 961678FD2CFF100D00B2B6DF /* Products */; @@ -316,6 +393,7 @@ 9616790E2CFF100E00B2B6DF /* Coder DesktopTests */, 961679182CFF100E00B2B6DF /* Coder DesktopUITests */, 9616792F2CFF117300B2B6DF /* VPN */, + 961679D82D030E1D00B2B6DF /* ProtoTests */, ); }; /* End PBXProject section */ @@ -349,6 +427,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 961679D72D030E1D00B2B6DF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +465,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 961679D52D030E1D00B2B6DF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -402,6 +494,11 @@ isa = PBXTargetDependency; productRef = AA8BC33B2D0060E700E1ABAA /* SwiftLintBuildToolPlugin */; }; + 961679DE2D030E1D00B2B6DF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 961678FB2CFF100D00B2B6DF /* Coder Desktop */; + targetProxy = 961679DD2D030E1D00B2B6DF /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -701,6 +798,40 @@ }; name = Release; }; + 961679E02D030E1D00B2B6DF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 4399GN35BJ; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop"; + }; + name = Debug; + }; + 961679E12D030E1D00B2B6DF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 4399GN35BJ; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.ProtoTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Coder Desktop.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Coder Desktop"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -749,6 +880,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 961679DF2D030E1D00B2B6DF /* Build configuration list for PBXNativeTarget "ProtoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 961679E02D030E1D00B2B6DF /* Debug */, + 961679E12D030E1D00B2B6DF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -792,6 +932,14 @@ minimumVersion = 5.10.2; }; }; + 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-protobuf.git"; + requirement = { + kind = exactVersion; + version = 1.28.2; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -820,6 +968,26 @@ package = AAD720CE2D0816B200F6304D /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + 961679522CFF207900B2B6DF /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + 961679542CFF207900B2B6DF /* SwiftProtobufPluginLibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobufPluginLibrary; + }; + 961679E22D03144900B2B6DF /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + 961679E42D03144C00B2B6DF /* SwiftProtobufPluginLibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 961679512CFF207900B2B6DF /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobufPluginLibrary; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 961678F42CFF100D00B2B6DF /* Project object */; diff --git a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3d9b7f4..cb887ce 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -27,6 +27,15 @@ "revision" : "e0c7eebc5a4465a3c4680764f26b7a61f567cdaf" } }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "ebc7251dd5b37f627c93698e4374084d98409633", + "version" : "1.28.2" + } + }, { "identity" : "swiftlintplugins", "kind" : "remoteSourceControl", @@ -41,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nalexn/ViewInspector", "state" : { - "revision" : "5acfa0a3c095ac9ad050abe51c60d1831e8321da", - "version" : "0.10.0" + "revision" : "788e7879d38a839c4e348ab0762dcc0364e646a2", + "version" : "0.10.1" } } ], diff --git a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme index 004582c..7671268 100644 --- a/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme +++ b/Coder Desktop/Coder Desktop.xcodeproj/xcshareddata/xcschemes/Coder Desktop.xcscheme @@ -57,6 +57,17 @@ ReferencedContainer = "container:Coder Desktop.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Coder Desktop/Proto/Receiver.swift b/Coder Desktop/Proto/Receiver.swift new file mode 100644 index 0000000..89ea0d3 --- /dev/null +++ b/Coder Desktop/Proto/Receiver.swift @@ -0,0 +1,94 @@ +import Foundation +import SwiftProtobuf +import os + +/// An actor that reads data from a `DispatchIO` channel, and deserializes it into VPN protocol messages. +actor Receiver { + private let dispatch: DispatchIO + private let queue: DispatchQueue + private var running = false + private let logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "proto") + + /// Creates an instance using the given `DispatchIO` channel and queue. + init(dispatch: DispatchIO, queue: DispatchQueue) { + self.dispatch = dispatch + self.queue = queue + } + + /// Reads the protobuf message length from the `DispatchIO`, decodes it and returns it. + private func readLen() async throws -> UInt32 { + let lenD: Data = try await withCheckedThrowingContinuation { continuation in + var lenData = Data() + dispatch.read(offset: 0, length: 4, queue: queue) {done, data, error in + guard error == 0 else { + let errStrPtr = strerror(error) + let errStr = String(validatingUTF8: errStrPtr!)! + continuation.resume(throwing: ReceiveError.readError(errStr)) + return + } + lenData.append(contentsOf: data!) + if done { + continuation.resume(returning: lenData) + } + } + } + return try deserializeLen(lenD) + } + + /// Reads a protobuf message from the `DispatchIO` of the given length, then decodes it and returns it. + private func readMsg(_ length: UInt32) async throws -> RecvMsg { + let msgData: Data = try await withCheckedThrowingContinuation { continuation in + var msgData = Data() + dispatch.read(offset: 0, length: Int(length), queue: queue) {done, data, error in + guard error == 0 else { + let errStrPtr = strerror(error) + let errStr = String(validatingUTF8: errStrPtr!)! + continuation.resume(throwing: ReceiveError.readError(errStr)) + return + } + msgData.append(contentsOf: data!) + if done { + continuation.resume(returning: msgData) + } + } + } + return try RecvMsg(serializedBytes: msgData) + } + + /// 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 -> AsyncStream { + if running { + throw ReceiveError.alreadyRunning + } + running = true + return AsyncStream( + unfolding: { + do { + let length = try await self.readLen() + return try await self.readMsg(length) + } catch { + self.logger.error("failed to read proto message: \(error)") + return nil + } + }, + onCancel: { + self.logger.debug("async stream canceled") + self.dispatch.close() + } + ) + } +} + +enum ReceiveError: Error { + case readError(String) + case invalidLength + case alreadyRunning +} + +func deserializeLen(_ data: Data) throws -> UInt32 { + if data.count != 4 { + throw ReceiveError.invalidLength + } + return UInt32(data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) +} diff --git a/Coder Desktop/Proto/Sender.swift b/Coder Desktop/Proto/Sender.swift new file mode 100644 index 0000000..2a1013e --- /dev/null +++ b/Coder Desktop/Proto/Sender.swift @@ -0,0 +1,33 @@ +import Foundation +import SwiftProtobuf + +/// A actor that serializes and sends VPN protocol messages over a `FileHandle`, which is typically +/// the write-side of a `Pipe`. +actor Sender { + private let writeFD: FileHandle + + init(writeFD: FileHandle) { + self.writeFD = writeFD + } + + func send(_ msg: SendMsg) throws { + let data = try msg.serializedData() + let length = serializeLen(UInt32(data.count)) + try writeFD.write(contentsOf: length) + try writeFD.write(contentsOf: data) + } + + func close() throws { + try writeFD.close() + } +} + +/// Returns the given length as Data suitable to be serialized. Encodes as an unsigned 32-bit big-endian integer. +func serializeLen(_ len: UInt32) -> Data { + var out = Data(count: 4) + out[0] = UInt8(len >> 24 & 0xFF) + out[1] = UInt8(len >> 16 & 0xFF) + out[2] = UInt8(len >> 8 & 0xFF) + out[3] = UInt8(len & 0xFF) + return out +} diff --git a/Coder Desktop/Proto/Speaker.swift b/Coder Desktop/Proto/Speaker.swift new file mode 100644 index 0000000..8c2edd3 --- /dev/null +++ b/Coder Desktop/Proto/Speaker.swift @@ -0,0 +1,339 @@ +import Foundation +import SwiftProtobuf +import os + +let newLine = 0x0a +let headerPreamble = "codervpn" + +/// A message that has the `rpc` property for recording participation in a unary RPC. +protocol RPCMessage { + var rpc: Vpn_RPC {get set} + /// Returns true if `rpc` has been explicitly set. + var hasRpc: Bool {get} +} + +extension Vpn_TunnelMessage: RPCMessage {} +extension Vpn_ManagerMessage: RPCMessage {} + +/// A role within the VPN protocol. Determines what message types are allowed to be sent and recieved. +enum ProtoRole: String { + case manager + case tunnel +} + +/// A version of the VPN protocol that can be negotiated. +struct ProtoVersion: CustomStringConvertible, Equatable, Codable { + let major: Int + let minor: Int + + var description: String {"\(major).\(minor)"} + + init(_ major: Int, _ minor: Int) { + self.major = major + self.minor = minor + } + + init(parse str: String) throws { + let parts = str.split(separator: ".").map({Int($0)}) + if parts.count != 2 { + throw HandshakeError.invalidVersion(str) + } + guard let major = parts[0] else { + throw HandshakeError.invalidVersion(str) + } + guard let minor = parts[1] else { + throw HandshakeError.invalidVersion(str) + } + self.major = major + self.minor = minor + } +} + +/// An abstract base class for implementations that need to communicate using the VPN protocol. +class Speaker { + private let logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "proto") + private let writeFD: FileHandle + private let readFD: FileHandle + private let dispatch: DispatchIO + private let queue: DispatchQueue = .global(qos: .utility) + private let sender: Sender + private let receiver: Receiver + private let secretary = RPCSecretary() + let role: ProtoRole + + /// Creates an instance that communicates over the provided file handles. + init(writeFD: FileHandle, readFD: FileHandle) { + self.writeFD = writeFD + self.readFD = readFD + self.sender = Sender(writeFD: writeFD) + self.dispatch = DispatchIO( + type: .stream, + fileDescriptor: readFD.fileDescriptor, + queue: queue, + cleanupHandler: {_ in + do { + try readFD.close() + } catch { + // TODO + } + }) + self.receiver = Receiver(dispatch: self.dispatch, queue: self.queue) + if SendMsg.self == Vpn_TunnelMessage.self { + self.role = .tunnel + } else { + self.role = .manager + } + } + + /// Does the VPN Protocol handshake and validates the result + func handshake() async throws { + let hndsh = Handshaker(writeFD: writeFD, dispatch: dispatch, queue: queue, role: role) + // ignore the version for now because we know it can only be 1.0 + try _ = await hndsh.handshake() + } + + /// Reads and handles protocol messages. + func readLoop() async throws { + for try await msg in try await self.receiver.messages() { + guard msg.hasRpc else { + self.handleMessage(msg) + continue + } + guard msg.rpc.msgID == 0 else { + let req = RPCRequest(req: msg, sender: self.sender) + self.handleRPC(req) + continue + } + guard msg.rpc.responseTo == 0 else { + self.logger.debug("got RPC reply for msgID \(msg.rpc.responseTo)") + do throws(RPCError) { + try await self.secretary.route(reply: msg) + } catch { + self.logger.error( + "couldn't route RPC reply for \(msg.rpc.responseTo): \(error)") + } + continue + } + } + } + + /// Handles a single non-RPC message. It is expected that subclasses override this method with their own handlers. + func handleMessage(_ msg: RecvMsg) { + // just log + self.logger.debug("got non-RPC message \(msg.textFormatString())") + } + + /// Handle a single RPC request. It is expected that subclasses override this method with their own handlers. + func handleRPC(_ req: RPCRequest) { + // just log + self.logger.debug("got RPC message \(req.msg.textFormatString())") + } + + /// Send a unary RPC message and handle the response + func unaryRPC(_ req: SendMsg) async throws -> RecvMsg { + return try await withCheckedThrowingContinuation { continuation in + Task { + let msgID = await self.secretary.record(continuation: continuation) + var req = req + req.rpc = Vpn_RPC() + req.rpc.msgID = msgID + do { + self.logger.debug("sending RPC with msgID: \(msgID)") + try await self.sender.send(req) + } catch { + self.logger.warning("failed to send RPC with msgID: \(msgID): \(error)") + await self.secretary.erase(id: req.rpc.msgID) + continuation.resume(throwing: error) + } + self.logger.debug("sent RPC with msgID: \(msgID)") + } + } + } + + func closeWrite() { + do { + try self.writeFD.close() + } catch { + logger.error("failed to close write file handle: \(error)") + } + } + + func closeRead() { + do { + try self.readFD.close() + } catch { + logger.error("failed to close read file handle: \(error)") + } + } +} + +/// A class that performs the initial VPN protocol handshake and version negotiation. +class Handshaker { + private let writeFD: FileHandle + private let dispatch: DispatchIO + private var theirData: Data = Data() + private let versions: [ProtoVersion] + private let role: ProtoRole + private var continuation: CheckedContinuation? + private let queue: DispatchQueue + + init (writeFD: FileHandle, dispatch: DispatchIO, queue: DispatchQueue, + role: ProtoRole, + versions: [ProtoVersion] = [.init(1, 0)] + ) { + self.writeFD = writeFD + self.dispatch = dispatch + self.role = role + self.queue = queue + self.versions = versions + } + + /// Performs the initial VPN protocol handshake, returning the negotiated `ProtoVersion` that we should use. + func handshake() async throws -> ProtoVersion { + // kick off the read async before we try to write, synchronously, so we don't deadlock, both + // waiting to write with nobody reading. + async let theirs = try withCheckedThrowingContinuation { cont in + continuation = cont + // send in a nil read to kick us off + handleRead(false, nil, 0) + } + + let vStr = versions.map({$0.description}).joined(separator: ",") + let ours = String(format: "\(headerPreamble) \(role) \(vStr)\n") + try writeFD.write(contentsOf: ours.data(using: .utf8)!) + + let theirData = try await theirs + guard let theirsString = String(bytes: theirData, encoding: .utf8) else { + throw HandshakeError.invalidHeader(" ProtoVersion { + let parts = header.split(separator: " ") + guard parts.count == 3 else { + throw HandshakeError.invalidHeader("expected 3 parts: \(header)") + } + guard parts[0] == headerPreamble else { + throw HandshakeError.invalidHeader("expected \(headerPreamble) but got \(parts[0])") + } + var expectedRole = ProtoRole.manager + if self.role == .manager { + expectedRole = .tunnel + } + guard parts[1] == expectedRole.rawValue else { + throw HandshakeError.wrongRole("expected \(expectedRole) but got \(parts[1])") + } + let theirVersions = try parts[2] + .split(separator: ",") + .map({try ProtoVersion(parse: String($0))}) + return try pickVersion(ours: versions, theirs: theirVersions) + } +} + +func pickVersion(ours: [ProtoVersion], theirs: [ProtoVersion]) throws -> ProtoVersion { + for our in ours.reversed() { + for their in theirs.reversed() where our.major == their.major { + if our.minor < their.minor { + return our + } + return their + } + } + throw HandshakeError.unsupportedVersion(theirs) +} + +enum HandshakeError: Error { + case readError(String) + case invalidHeader(String) + case wrongRole(String) + case invalidVersion(String) + case unsupportedVersion([ProtoVersion]) +} + +struct RPCRequest { + let msg: RecvMsg + private let sender: Sender + + public init(req: RecvMsg, sender: Sender) { + self.msg = req + self.sender = sender + } + + func sendReply(_ reply: SendMsg) async throws { + var reply = reply + reply.rpc.responseTo = msg.rpc.msgID + try await sender.send(reply) + } +} + +enum RPCError: Error { + case missingRPC + case notARequest + case notAResponse + case unknownResponseID(UInt64) + case shutdown +} + +/// An actor to record outgoing RPCs and route their replies to the original sender +actor RPCSecretary { + private var continuations: [UInt64: CheckedContinuation] = [:] + private var nextMsgID: UInt64 = 1 + + func record(continuation: CheckedContinuation) -> UInt64 { + let id = nextMsgID + nextMsgID += 1 + continuations[id] = continuation + return id + } + + func erase(id: UInt64) { + continuations[id] = nil + } + + func shutdown() { + for cont in continuations.values { + cont.resume(throwing: RPCError.shutdown) + } + continuations = [:] + } + + func route(reply: RecvMsg) throws(RPCError) { + guard reply.hasRpc else { + throw RPCError.missingRPC + } + guard reply.rpc.responseTo != 0 else { + throw RPCError.notAResponse + } + guard let cont = continuations[reply.rpc.responseTo] else { + throw RPCError.unknownResponseID(reply.rpc.responseTo) + } + continuations[reply.rpc.responseTo] = nil + cont.resume(returning: reply) + } +} diff --git a/Coder Desktop/Proto/vpn.pb.swift b/Coder Desktop/Proto/vpn.pb.swift new file mode 100644 index 0000000..184abfb --- /dev/null +++ b/Coder Desktop/Proto/vpn.pb.swift @@ -0,0 +1,1766 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Coder Desktop/Proto/vpn.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// RPC allows a very simple unary request/response RPC mechanism. The requester generates a unique +/// msg_id which it sets on the request, the responder sets response_to that msg_id on the response +/// message +struct Vpn_RPC: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var msgID: UInt64 = 0 + + var responseTo: UInt64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// ManagerMessage is a message from the manager (to the tunnel). +struct Vpn_ManagerMessage: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var rpc: Vpn_RPC { + get {return _rpc ?? Vpn_RPC()} + set {_rpc = newValue} + } + /// Returns true if `rpc` has been explicitly set. + var hasRpc: Bool {return self._rpc != nil} + /// Clears the value of `rpc`. Subsequent reads from it will return its default value. + mutating func clearRpc() {self._rpc = nil} + + var msg: Vpn_ManagerMessage.OneOf_Msg? = nil + + var getPeerUpdate: Vpn_GetPeerUpdate { + get { + if case .getPeerUpdate(let v)? = msg {return v} + return Vpn_GetPeerUpdate() + } + set {msg = .getPeerUpdate(newValue)} + } + + var networkSettings: Vpn_NetworkSettingsResponse { + get { + if case .networkSettings(let v)? = msg {return v} + return Vpn_NetworkSettingsResponse() + } + set {msg = .networkSettings(newValue)} + } + + var start: Vpn_StartRequest { + get { + if case .start(let v)? = msg {return v} + return Vpn_StartRequest() + } + set {msg = .start(newValue)} + } + + var stop: Vpn_StopRequest { + get { + if case .stop(let v)? = msg {return v} + return Vpn_StopRequest() + } + set {msg = .stop(newValue)} + } + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum OneOf_Msg: Equatable, Sendable { + case getPeerUpdate(Vpn_GetPeerUpdate) + case networkSettings(Vpn_NetworkSettingsResponse) + case start(Vpn_StartRequest) + case stop(Vpn_StopRequest) + + } + + init() {} + + fileprivate var _rpc: Vpn_RPC? = nil +} + +/// TunnelMessage is a message from the tunnel (to the manager). +struct Vpn_TunnelMessage: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var rpc: Vpn_RPC { + get {return _rpc ?? Vpn_RPC()} + set {_rpc = newValue} + } + /// Returns true if `rpc` has been explicitly set. + var hasRpc: Bool {return self._rpc != nil} + /// Clears the value of `rpc`. Subsequent reads from it will return its default value. + mutating func clearRpc() {self._rpc = nil} + + var msg: Vpn_TunnelMessage.OneOf_Msg? = nil + + var log: Vpn_Log { + get { + if case .log(let v)? = msg {return v} + return Vpn_Log() + } + set {msg = .log(newValue)} + } + + var peerUpdate: Vpn_PeerUpdate { + get { + if case .peerUpdate(let v)? = msg {return v} + return Vpn_PeerUpdate() + } + set {msg = .peerUpdate(newValue)} + } + + var networkSettings: Vpn_NetworkSettingsRequest { + get { + if case .networkSettings(let v)? = msg {return v} + return Vpn_NetworkSettingsRequest() + } + set {msg = .networkSettings(newValue)} + } + + var start: Vpn_StartResponse { + get { + if case .start(let v)? = msg {return v} + return Vpn_StartResponse() + } + set {msg = .start(newValue)} + } + + var stop: Vpn_StopResponse { + get { + if case .stop(let v)? = msg {return v} + return Vpn_StopResponse() + } + set {msg = .stop(newValue)} + } + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum OneOf_Msg: Equatable, Sendable { + case log(Vpn_Log) + case peerUpdate(Vpn_PeerUpdate) + case networkSettings(Vpn_NetworkSettingsRequest) + case start(Vpn_StartResponse) + case stop(Vpn_StopResponse) + + } + + init() {} + + fileprivate var _rpc: Vpn_RPC? = nil +} + +/// Log is a log message generated by the tunnel. The manager should log it to the system log. It is +/// one-way tunnel -> manager with no response. +struct Vpn_Log: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var level: Vpn_Log.Level = .debug + + var message: String = String() + + var loggerNames: [String] = [] + + var fields: [Vpn_Log.Field] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { + typealias RawValue = Int + + /// these are designed to match slog levels + case debug // = 0 + case info // = 1 + case warn // = 2 + case error // = 3 + case critical // = 4 + case fatal // = 5 + case UNRECOGNIZED(Int) + + init() { + self = .debug + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .debug + case 1: self = .info + case 2: self = .warn + case 3: self = .error + case 4: self = .critical + case 5: self = .fatal + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .debug: return 0 + case .info: return 1 + case .warn: return 2 + case .error: return 3 + case .critical: return 4 + case .fatal: return 5 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + static let allCases: [Vpn_Log.Level] = [ + .debug, + .info, + .warn, + .error, + .critical, + .fatal, + ] + + } + + struct Field: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var name: String = String() + + var value: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + init() {} +} + +/// GetPeerUpdate asks for a PeerUpdate with a full set of data. +struct Vpn_GetPeerUpdate: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// PeerUpdate is an update about workspaces and agents connected via the tunnel. It is generated in +/// response to GetPeerUpdate (which dumps the full set). It is also generated on any changes (not in +/// response to any request). +struct Vpn_PeerUpdate: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var upsertedWorkspaces: [Vpn_Workspace] = [] + + var upsertedAgents: [Vpn_Agent] = [] + + var deletedWorkspaces: [Vpn_Workspace] = [] + + var deletedAgents: [Vpn_Agent] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct Vpn_Workspace: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// UUID + var id: Data = Data() + + var name: String = String() + + var status: Vpn_Workspace.Status = .unknown + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum Status: SwiftProtobuf.Enum, Swift.CaseIterable { + typealias RawValue = Int + case unknown // = 0 + case pending // = 1 + case starting // = 2 + case running // = 3 + case stopping // = 4 + case stopped // = 5 + case failed // = 6 + case canceling // = 7 + case canceled // = 8 + case deleting // = 9 + case deleted // = 10 + case UNRECOGNIZED(Int) + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .pending + case 2: self = .starting + case 3: self = .running + case 4: self = .stopping + case 5: self = .stopped + case 6: self = .failed + case 7: self = .canceling + case 8: self = .canceled + case 9: self = .deleting + case 10: self = .deleted + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .pending: return 1 + case .starting: return 2 + case .running: return 3 + case .stopping: return 4 + case .stopped: return 5 + case .failed: return 6 + case .canceling: return 7 + case .canceled: return 8 + case .deleting: return 9 + case .deleted: return 10 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + static let allCases: [Vpn_Workspace.Status] = [ + .unknown, + .pending, + .starting, + .running, + .stopping, + .stopped, + .failed, + .canceling, + .canceled, + .deleting, + .deleted, + ] + + } + + init() {} +} + +struct Vpn_Agent: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// UUID + var id: Data = Data() + + var name: String = String() + + /// UUID + var workspaceID: Data = Data() + + var fqdn: String = String() + + var ipAddrs: [String] = [] + + /// last_handshake is the primary indicator of whether we are connected to a peer. Zero value or + /// anything longer than 5 minutes ago means there is a problem. + var lastHandshake: SwiftProtobuf.Google_Protobuf_Timestamp { + get {return _lastHandshake ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + set {_lastHandshake = newValue} + } + /// Returns true if `lastHandshake` has been explicitly set. + var hasLastHandshake: Bool {return self._lastHandshake != nil} + /// Clears the value of `lastHandshake`. Subsequent reads from it will return its default value. + mutating func clearLastHandshake() {self._lastHandshake = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _lastHandshake: SwiftProtobuf.Google_Protobuf_Timestamp? = nil +} + +/// NetworkSettingsRequest is based on +/// https://developer.apple.com/documentation/networkextension/nepackettunnelnetworksettings for +/// macOS. It is a request/response message with response NetworkSettingsResponse +struct Vpn_NetworkSettingsRequest: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var tunnelOverheadBytes: UInt32 { + get {return _storage._tunnelOverheadBytes} + set {_uniqueStorage()._tunnelOverheadBytes = newValue} + } + + var mtu: UInt32 { + get {return _storage._mtu} + set {_uniqueStorage()._mtu = newValue} + } + + var dnsSettings: Vpn_NetworkSettingsRequest.DNSSettings { + get {return _storage._dnsSettings ?? Vpn_NetworkSettingsRequest.DNSSettings()} + set {_uniqueStorage()._dnsSettings = newValue} + } + /// Returns true if `dnsSettings` has been explicitly set. + var hasDnsSettings: Bool {return _storage._dnsSettings != nil} + /// Clears the value of `dnsSettings`. Subsequent reads from it will return its default value. + mutating func clearDnsSettings() {_uniqueStorage()._dnsSettings = nil} + + var tunnelRemoteAddress: String { + get {return _storage._tunnelRemoteAddress} + set {_uniqueStorage()._tunnelRemoteAddress = newValue} + } + + var ipv4Settings: Vpn_NetworkSettingsRequest.IPv4Settings { + get {return _storage._ipv4Settings ?? Vpn_NetworkSettingsRequest.IPv4Settings()} + set {_uniqueStorage()._ipv4Settings = newValue} + } + /// Returns true if `ipv4Settings` has been explicitly set. + var hasIpv4Settings: Bool {return _storage._ipv4Settings != nil} + /// Clears the value of `ipv4Settings`. Subsequent reads from it will return its default value. + mutating func clearIpv4Settings() {_uniqueStorage()._ipv4Settings = nil} + + var ipv6Settings: Vpn_NetworkSettingsRequest.IPv6Settings { + get {return _storage._ipv6Settings ?? Vpn_NetworkSettingsRequest.IPv6Settings()} + set {_uniqueStorage()._ipv6Settings = newValue} + } + /// Returns true if `ipv6Settings` has been explicitly set. + var hasIpv6Settings: Bool {return _storage._ipv6Settings != nil} + /// Clears the value of `ipv6Settings`. Subsequent reads from it will return its default value. + mutating func clearIpv6Settings() {_uniqueStorage()._ipv6Settings = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + struct DNSSettings: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var servers: [String] = [] + + var searchDomains: [String] = [] + + /// domain_name is the primary domain name of the tunnel + var domainName: String = String() + + var matchDomains: [String] = [] + + /// match_domains_no_search specifies if the domains in the matchDomains list should not be + /// appended to the resolver’s list of search domains. + var matchDomainsNoSearch: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + struct IPv4Settings: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var addrs: [String] = [] + + var subnetMasks: [String] = [] + + /// router is the next-hop router in dotted-decimal format + var router: String = String() + + var includedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] + + var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + struct IPv4Route: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var destination: String = String() + + var mask: String = String() + + /// router is the next-hop router in dotted-decimal format + var router: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + init() {} + } + + struct IPv6Settings: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var addrs: [String] = [] + + var prefixLengths: [UInt32] = [] + + var includedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] + + var excludedRoutes: [Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + struct IPv6Route: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var destination: String = String() + + var prefixLength: UInt32 = 0 + + /// router is the address of the next-hop + var router: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + + init() {} + } + + init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +/// NetworkSettingsResponse is the response from the manager to the tunnel for a +/// NetworkSettingsRequest +struct Vpn_NetworkSettingsResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var success: Bool = false + + var errorMessage: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// StartRequest is a request from the manager to start the tunnel. The tunnel replies with a +/// StartResponse. +struct Vpn_StartRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var tunnelFileDescriptor: Int32 = 0 + + var coderURL: String = String() + + var apiToken: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct Vpn_StartResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var success: Bool = false + + var errorMessage: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// StopRequest is a request from the manager to stop the tunnel. The tunnel replies with a +/// StopResponse. +struct Vpn_StopRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// StopResponse is a response to stopping the tunnel. After sending this response, the tunnel closes +/// its side of the bidirectional stream for writing. +struct Vpn_StopResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var success: Bool = false + + var errorMessage: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "vpn" + +extension Vpn_RPC: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".RPC" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "msg_id"), + 2: .standard(proto: "response_to"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.msgID) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.responseTo) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.msgID != 0 { + try visitor.visitSingularUInt64Field(value: self.msgID, fieldNumber: 1) + } + if self.responseTo != 0 { + try visitor.visitSingularUInt64Field(value: self.responseTo, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_RPC, rhs: Vpn_RPC) -> Bool { + if lhs.msgID != rhs.msgID {return false} + if lhs.responseTo != rhs.responseTo {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_ManagerMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ManagerMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "rpc"), + 2: .standard(proto: "get_peer_update"), + 3: .standard(proto: "network_settings"), + 4: .same(proto: "start"), + 5: .same(proto: "stop"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._rpc) }() + case 2: try { + var v: Vpn_GetPeerUpdate? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .getPeerUpdate(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .getPeerUpdate(v) + } + }() + case 3: try { + var v: Vpn_NetworkSettingsResponse? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .networkSettings(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .networkSettings(v) + } + }() + case 4: try { + var v: Vpn_StartRequest? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .start(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .start(v) + } + }() + case 5: try { + var v: Vpn_StopRequest? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .stop(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .stop(v) + } + }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._rpc { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + switch self.msg { + case .getPeerUpdate?: try { + guard case .getPeerUpdate(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .networkSettings?: try { + guard case .networkSettings(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .start?: try { + guard case .start(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case .stop?: try { + guard case .stop(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_ManagerMessage, rhs: Vpn_ManagerMessage) -> Bool { + if lhs._rpc != rhs._rpc {return false} + if lhs.msg != rhs.msg {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_TunnelMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TunnelMessage" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "rpc"), + 2: .same(proto: "log"), + 3: .standard(proto: "peer_update"), + 4: .standard(proto: "network_settings"), + 5: .same(proto: "start"), + 6: .same(proto: "stop"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._rpc) }() + case 2: try { + var v: Vpn_Log? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .log(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .log(v) + } + }() + case 3: try { + var v: Vpn_PeerUpdate? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .peerUpdate(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .peerUpdate(v) + } + }() + case 4: try { + var v: Vpn_NetworkSettingsRequest? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .networkSettings(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .networkSettings(v) + } + }() + case 5: try { + var v: Vpn_StartResponse? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .start(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .start(v) + } + }() + case 6: try { + var v: Vpn_StopResponse? + var hadOneofValue = false + if let current = self.msg { + hadOneofValue = true + if case .stop(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.msg = .stop(v) + } + }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._rpc { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + switch self.msg { + case .log?: try { + guard case .log(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case .peerUpdate?: try { + guard case .peerUpdate(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .networkSettings?: try { + guard case .networkSettings(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() + case .start?: try { + guard case .start(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + }() + case .stop?: try { + guard case .stop(let v)? = self.msg else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_TunnelMessage, rhs: Vpn_TunnelMessage) -> Bool { + if lhs._rpc != rhs._rpc {return false} + if lhs.msg != rhs.msg {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_Log: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Log" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "level"), + 2: .same(proto: "message"), + 3: .standard(proto: "logger_names"), + 4: .same(proto: "fields"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.level) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() + case 3: try { try decoder.decodeRepeatedStringField(value: &self.loggerNames) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.fields) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.level != .debug { + try visitor.visitSingularEnumField(value: self.level, fieldNumber: 1) + } + if !self.message.isEmpty { + try visitor.visitSingularStringField(value: self.message, fieldNumber: 2) + } + if !self.loggerNames.isEmpty { + try visitor.visitRepeatedStringField(value: self.loggerNames, fieldNumber: 3) + } + if !self.fields.isEmpty { + try visitor.visitRepeatedMessageField(value: self.fields, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_Log, rhs: Vpn_Log) -> Bool { + if lhs.level != rhs.level {return false} + if lhs.message != rhs.message {return false} + if lhs.loggerNames != rhs.loggerNames {return false} + if lhs.fields != rhs.fields {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_Log.Level: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DEBUG"), + 1: .same(proto: "INFO"), + 2: .same(proto: "WARN"), + 3: .same(proto: "ERROR"), + 4: .same(proto: "CRITICAL"), + 5: .same(proto: "FATAL"), + ] +} + +extension Vpn_Log.Field: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_Log.protoMessageName + ".Field" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "name"), + 2: .same(proto: "value"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.value) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) + } + if !self.value.isEmpty { + try visitor.visitSingularStringField(value: self.value, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_Log.Field, rhs: Vpn_Log.Field) -> Bool { + if lhs.name != rhs.name {return false} + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_GetPeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".GetPeerUpdate" + static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_GetPeerUpdate, rhs: Vpn_GetPeerUpdate) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_PeerUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PeerUpdate" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "upserted_workspaces"), + 2: .standard(proto: "upserted_agents"), + 3: .standard(proto: "deleted_workspaces"), + 4: .standard(proto: "deleted_agents"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.upsertedWorkspaces) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.upsertedAgents) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.deletedWorkspaces) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.deletedAgents) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.upsertedWorkspaces.isEmpty { + try visitor.visitRepeatedMessageField(value: self.upsertedWorkspaces, fieldNumber: 1) + } + if !self.upsertedAgents.isEmpty { + try visitor.visitRepeatedMessageField(value: self.upsertedAgents, fieldNumber: 2) + } + if !self.deletedWorkspaces.isEmpty { + try visitor.visitRepeatedMessageField(value: self.deletedWorkspaces, fieldNumber: 3) + } + if !self.deletedAgents.isEmpty { + try visitor.visitRepeatedMessageField(value: self.deletedAgents, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_PeerUpdate, rhs: Vpn_PeerUpdate) -> Bool { + if lhs.upsertedWorkspaces != rhs.upsertedWorkspaces {return false} + if lhs.upsertedAgents != rhs.upsertedAgents {return false} + if lhs.deletedWorkspaces != rhs.deletedWorkspaces {return false} + if lhs.deletedAgents != rhs.deletedAgents {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_Workspace: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Workspace" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "name"), + 3: .same(proto: "status"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.id) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.name) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.status) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.id.isEmpty { + try visitor.visitSingularBytesField(value: self.id, fieldNumber: 1) + } + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 2) + } + if self.status != .unknown { + try visitor.visitSingularEnumField(value: self.status, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_Workspace, rhs: Vpn_Workspace) -> Bool { + if lhs.id != rhs.id {return false} + if lhs.name != rhs.name {return false} + if lhs.status != rhs.status {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_Workspace.Status: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN"), + 1: .same(proto: "PENDING"), + 2: .same(proto: "STARTING"), + 3: .same(proto: "RUNNING"), + 4: .same(proto: "STOPPING"), + 5: .same(proto: "STOPPED"), + 6: .same(proto: "FAILED"), + 7: .same(proto: "CANCELING"), + 8: .same(proto: "CANCELED"), + 9: .same(proto: "DELETING"), + 10: .same(proto: "DELETED"), + ] +} + +extension Vpn_Agent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Agent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "name"), + 3: .standard(proto: "workspace_id"), + 4: .same(proto: "fqdn"), + 5: .standard(proto: "ip_addrs"), + 6: .standard(proto: "last_handshake"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.id) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.name) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.workspaceID) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.fqdn) }() + case 5: try { try decoder.decodeRepeatedStringField(value: &self.ipAddrs) }() + case 6: try { try decoder.decodeSingularMessageField(value: &self._lastHandshake) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.id.isEmpty { + try visitor.visitSingularBytesField(value: self.id, fieldNumber: 1) + } + if !self.name.isEmpty { + try visitor.visitSingularStringField(value: self.name, fieldNumber: 2) + } + if !self.workspaceID.isEmpty { + try visitor.visitSingularBytesField(value: self.workspaceID, fieldNumber: 3) + } + if !self.fqdn.isEmpty { + try visitor.visitSingularStringField(value: self.fqdn, fieldNumber: 4) + } + if !self.ipAddrs.isEmpty { + try visitor.visitRepeatedStringField(value: self.ipAddrs, fieldNumber: 5) + } + try { if let v = self._lastHandshake { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_Agent, rhs: Vpn_Agent) -> Bool { + if lhs.id != rhs.id {return false} + if lhs.name != rhs.name {return false} + if lhs.workspaceID != rhs.workspaceID {return false} + if lhs.fqdn != rhs.fqdn {return false} + if lhs.ipAddrs != rhs.ipAddrs {return false} + if lhs._lastHandshake != rhs._lastHandshake {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".NetworkSettingsRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "tunnel_overhead_bytes"), + 2: .same(proto: "mtu"), + 3: .standard(proto: "dns_settings"), + 4: .standard(proto: "tunnel_remote_address"), + 5: .standard(proto: "ipv4_settings"), + 6: .standard(proto: "ipv6_settings"), + ] + + fileprivate class _StorageClass { + var _tunnelOverheadBytes: UInt32 = 0 + var _mtu: UInt32 = 0 + var _dnsSettings: Vpn_NetworkSettingsRequest.DNSSettings? = nil + var _tunnelRemoteAddress: String = String() + var _ipv4Settings: Vpn_NetworkSettingsRequest.IPv4Settings? = nil + var _ipv6Settings: Vpn_NetworkSettingsRequest.IPv6Settings? = nil + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _tunnelOverheadBytes = source._tunnelOverheadBytes + _mtu = source._mtu + _dnsSettings = source._dnsSettings + _tunnelRemoteAddress = source._tunnelRemoteAddress + _ipv4Settings = source._ipv4Settings + _ipv6Settings = source._ipv6Settings + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._tunnelOverheadBytes) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &_storage._mtu) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._dnsSettings) }() + case 4: try { try decoder.decodeSingularStringField(value: &_storage._tunnelRemoteAddress) }() + case 5: try { try decoder.decodeSingularMessageField(value: &_storage._ipv4Settings) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._ipv6Settings) }() + default: break + } + } + } + } + + func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if _storage._tunnelOverheadBytes != 0 { + try visitor.visitSingularUInt32Field(value: _storage._tunnelOverheadBytes, fieldNumber: 1) + } + if _storage._mtu != 0 { + try visitor.visitSingularUInt32Field(value: _storage._mtu, fieldNumber: 2) + } + try { if let v = _storage._dnsSettings { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if !_storage._tunnelRemoteAddress.isEmpty { + try visitor.visitSingularStringField(value: _storage._tunnelRemoteAddress, fieldNumber: 4) + } + try { if let v = _storage._ipv4Settings { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._ipv6Settings { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest, rhs: Vpn_NetworkSettingsRequest) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._tunnelOverheadBytes != rhs_storage._tunnelOverheadBytes {return false} + if _storage._mtu != rhs_storage._mtu {return false} + if _storage._dnsSettings != rhs_storage._dnsSettings {return false} + if _storage._tunnelRemoteAddress != rhs_storage._tunnelRemoteAddress {return false} + if _storage._ipv4Settings != rhs_storage._ipv4Settings {return false} + if _storage._ipv6Settings != rhs_storage._ipv6Settings {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest.DNSSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".DNSSettings" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "servers"), + 2: .standard(proto: "search_domains"), + 3: .standard(proto: "domain_name"), + 4: .standard(proto: "match_domains"), + 5: .standard(proto: "match_domains_no_search"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedStringField(value: &self.servers) }() + case 2: try { try decoder.decodeRepeatedStringField(value: &self.searchDomains) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.domainName) }() + case 4: try { try decoder.decodeRepeatedStringField(value: &self.matchDomains) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.matchDomainsNoSearch) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.servers.isEmpty { + try visitor.visitRepeatedStringField(value: self.servers, fieldNumber: 1) + } + if !self.searchDomains.isEmpty { + try visitor.visitRepeatedStringField(value: self.searchDomains, fieldNumber: 2) + } + if !self.domainName.isEmpty { + try visitor.visitSingularStringField(value: self.domainName, fieldNumber: 3) + } + if !self.matchDomains.isEmpty { + try visitor.visitRepeatedStringField(value: self.matchDomains, fieldNumber: 4) + } + if self.matchDomainsNoSearch != false { + try visitor.visitSingularBoolField(value: self.matchDomainsNoSearch, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest.DNSSettings, rhs: Vpn_NetworkSettingsRequest.DNSSettings) -> Bool { + if lhs.servers != rhs.servers {return false} + if lhs.searchDomains != rhs.searchDomains {return false} + if lhs.domainName != rhs.domainName {return false} + if lhs.matchDomains != rhs.matchDomains {return false} + if lhs.matchDomainsNoSearch != rhs.matchDomainsNoSearch {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest.IPv4Settings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv4Settings" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "addrs"), + 2: .standard(proto: "subnet_masks"), + 3: .same(proto: "router"), + 4: .standard(proto: "included_routes"), + 5: .standard(proto: "excluded_routes"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedStringField(value: &self.addrs) }() + case 2: try { try decoder.decodeRepeatedStringField(value: &self.subnetMasks) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.router) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.includedRoutes) }() + case 5: try { try decoder.decodeRepeatedMessageField(value: &self.excludedRoutes) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.addrs.isEmpty { + try visitor.visitRepeatedStringField(value: self.addrs, fieldNumber: 1) + } + if !self.subnetMasks.isEmpty { + try visitor.visitRepeatedStringField(value: self.subnetMasks, fieldNumber: 2) + } + if !self.router.isEmpty { + try visitor.visitSingularStringField(value: self.router, fieldNumber: 3) + } + if !self.includedRoutes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.includedRoutes, fieldNumber: 4) + } + if !self.excludedRoutes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.excludedRoutes, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings, rhs: Vpn_NetworkSettingsRequest.IPv4Settings) -> Bool { + if lhs.addrs != rhs.addrs {return false} + if lhs.subnetMasks != rhs.subnetMasks {return false} + if lhs.router != rhs.router {return false} + if lhs.includedRoutes != rhs.includedRoutes {return false} + if lhs.excludedRoutes != rhs.excludedRoutes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv4Settings.protoMessageName + ".IPv4Route" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "destination"), + 2: .same(proto: "mask"), + 3: .same(proto: "router"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.destination) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.mask) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.router) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.destination.isEmpty { + try visitor.visitSingularStringField(value: self.destination, fieldNumber: 1) + } + if !self.mask.isEmpty { + try visitor.visitSingularStringField(value: self.mask, fieldNumber: 2) + } + if !self.router.isEmpty { + try visitor.visitSingularStringField(value: self.router, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route, rhs: Vpn_NetworkSettingsRequest.IPv4Settings.IPv4Route) -> Bool { + if lhs.destination != rhs.destination {return false} + if lhs.mask != rhs.mask {return false} + if lhs.router != rhs.router {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest.IPv6Settings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_NetworkSettingsRequest.protoMessageName + ".IPv6Settings" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "addrs"), + 2: .standard(proto: "prefix_lengths"), + 3: .standard(proto: "included_routes"), + 4: .standard(proto: "excluded_routes"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedStringField(value: &self.addrs) }() + case 2: try { try decoder.decodeRepeatedUInt32Field(value: &self.prefixLengths) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.includedRoutes) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.excludedRoutes) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.addrs.isEmpty { + try visitor.visitRepeatedStringField(value: self.addrs, fieldNumber: 1) + } + if !self.prefixLengths.isEmpty { + try visitor.visitPackedUInt32Field(value: self.prefixLengths, fieldNumber: 2) + } + if !self.includedRoutes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.includedRoutes, fieldNumber: 3) + } + if !self.excludedRoutes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.excludedRoutes, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings, rhs: Vpn_NetworkSettingsRequest.IPv6Settings) -> Bool { + if lhs.addrs != rhs.addrs {return false} + if lhs.prefixLengths != rhs.prefixLengths {return false} + if lhs.includedRoutes != rhs.includedRoutes {return false} + if lhs.excludedRoutes != rhs.excludedRoutes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Vpn_NetworkSettingsRequest.IPv6Settings.protoMessageName + ".IPv6Route" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "destination"), + 2: .standard(proto: "prefix_length"), + 3: .same(proto: "router"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.destination) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.prefixLength) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.router) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.destination.isEmpty { + try visitor.visitSingularStringField(value: self.destination, fieldNumber: 1) + } + if self.prefixLength != 0 { + try visitor.visitSingularUInt32Field(value: self.prefixLength, fieldNumber: 2) + } + if !self.router.isEmpty { + try visitor.visitSingularStringField(value: self.router, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route, rhs: Vpn_NetworkSettingsRequest.IPv6Settings.IPv6Route) -> Bool { + if lhs.destination != rhs.destination {return false} + if lhs.prefixLength != rhs.prefixLength {return false} + if lhs.router != rhs.router {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_NetworkSettingsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".NetworkSettingsResponse" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "success"), + 2: .standard(proto: "error_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.success) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.success != false { + try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) + } + if !self.errorMessage.isEmpty { + try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_NetworkSettingsResponse, rhs: Vpn_NetworkSettingsResponse) -> Bool { + if lhs.success != rhs.success {return false} + if lhs.errorMessage != rhs.errorMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_StartRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StartRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "tunnel_file_descriptor"), + 2: .standard(proto: "coder_url"), + 3: .standard(proto: "api_token"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.tunnelFileDescriptor) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.coderURL) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.apiToken) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.tunnelFileDescriptor != 0 { + try visitor.visitSingularInt32Field(value: self.tunnelFileDescriptor, fieldNumber: 1) + } + if !self.coderURL.isEmpty { + try visitor.visitSingularStringField(value: self.coderURL, fieldNumber: 2) + } + if !self.apiToken.isEmpty { + try visitor.visitSingularStringField(value: self.apiToken, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_StartRequest, rhs: Vpn_StartRequest) -> Bool { + if lhs.tunnelFileDescriptor != rhs.tunnelFileDescriptor {return false} + if lhs.coderURL != rhs.coderURL {return false} + if lhs.apiToken != rhs.apiToken {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_StartResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StartResponse" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "success"), + 2: .standard(proto: "error_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.success) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.success != false { + try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) + } + if !self.errorMessage.isEmpty { + try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_StartResponse, rhs: Vpn_StartResponse) -> Bool { + if lhs.success != rhs.success {return false} + if lhs.errorMessage != rhs.errorMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_StopRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StopRequest" + static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + mutating func decodeMessage(decoder: inout D) throws { + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} + } + + func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_StopRequest, rhs: Vpn_StopRequest) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Vpn_StopResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StopResponse" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "success"), + 2: .standard(proto: "error_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.success) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.success != false { + try visitor.visitSingularBoolField(value: self.success, fieldNumber: 1) + } + if !self.errorMessage.isEmpty { + try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Vpn_StopResponse, rhs: Vpn_StopResponse) -> Bool { + if lhs.success != rhs.success {return false} + if lhs.errorMessage != rhs.errorMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Coder Desktop/Proto/vpn.proto b/Coder Desktop/Proto/vpn.proto new file mode 100644 index 0000000..1d21f7c --- /dev/null +++ b/Coder Desktop/Proto/vpn.proto @@ -0,0 +1,198 @@ +syntax = "proto3"; +option go_package = "github.com/coder/coder/v2/vpn"; +option csharp_namespace = "Coder.Desktop.Vpn.Proto"; + +import "google/protobuf/timestamp.proto"; + +package vpn; + +// The CoderVPN protocol operates over a bidirectional stream between a "manager" and a "tunnel." +// The manager is part of the Coder Desktop application and written in OS native code. It handles +// configuring the VPN and displaying status to the end user. The tunnel is written in Go and +// handles operating the actual tunnel, including reading and writing packets, & communicating with +// the Coder server control plane. + + +// RPC allows a very simple unary request/response RPC mechanism. The requester generates a unique +// msg_id which it sets on the request, the responder sets response_to that msg_id on the response +// message +message RPC { + uint64 msg_id = 1; + uint64 response_to = 2; +} + +// ManagerMessage is a message from the manager (to the tunnel). +message ManagerMessage { + RPC rpc = 1; + oneof msg { + GetPeerUpdate get_peer_update = 2; + NetworkSettingsResponse network_settings = 3; + StartRequest start = 4; + StopRequest stop = 5; + } +} + +// TunnelMessage is a message from the tunnel (to the manager). +message TunnelMessage { + RPC rpc = 1; + oneof msg { + Log log = 2; + PeerUpdate peer_update = 3; + NetworkSettingsRequest network_settings = 4; + StartResponse start = 5; + StopResponse stop = 6; + } +} + +// Log is a log message generated by the tunnel. The manager should log it to the system log. It is +// one-way tunnel -> manager with no response. +message Log { + enum Level { + // these are designed to match slog levels + DEBUG = 0; + INFO = 1; + WARN = 2; + ERROR = 3; + CRITICAL = 4; + FATAL = 5; + } + Level level = 1; + + string message = 2; + repeated string logger_names = 3; + + message Field { + string name = 1; + string value = 2; + } + repeated Field fields = 4; +} + +// GetPeerUpdate asks for a PeerUpdate with a full set of data. +message GetPeerUpdate {} + +// PeerUpdate is an update about workspaces and agents connected via the tunnel. It is generated in +// response to GetPeerUpdate (which dumps the full set). It is also generated on any changes (not in +// response to any request). +message PeerUpdate { + repeated Workspace upserted_workspaces = 1; + repeated Agent upserted_agents = 2; + repeated Workspace deleted_workspaces = 3; + repeated Agent deleted_agents = 4; +} + +message Workspace { + bytes id = 1; // UUID + string name = 2; + + enum Status { + UNKNOWN = 0; + PENDING = 1; + STARTING = 2; + RUNNING = 3; + STOPPING = 4; + STOPPED = 5; + FAILED = 6; + CANCELING = 7; + CANCELED = 8; + DELETING = 9; + DELETED = 10; + } + Status status = 3; +} + +message Agent { + bytes id = 1; // UUID + string name = 2; + bytes workspace_id = 3; // UUID + string fqdn = 4; + repeated string ip_addrs = 5; + // last_handshake is the primary indicator of whether we are connected to a peer. Zero value or + // anything longer than 5 minutes ago means there is a problem. + google.protobuf.Timestamp last_handshake = 6; +} + +// NetworkSettingsRequest is based on +// https://developer.apple.com/documentation/networkextension/nepackettunnelnetworksettings for +// macOS. It is a request/response message with response NetworkSettingsResponse +message NetworkSettingsRequest { + uint32 tunnel_overhead_bytes = 1; + uint32 mtu = 2; + + message DNSSettings { + repeated string servers = 1; + repeated string search_domains = 2; + // domain_name is the primary domain name of the tunnel + string domain_name = 3; + repeated string match_domains = 4; + // match_domains_no_search specifies if the domains in the matchDomains list should not be + // appended to the resolver’s list of search domains. + bool match_domains_no_search = 5; + } + DNSSettings dns_settings = 3; + + string tunnel_remote_address = 4; + + message IPv4Settings { + repeated string addrs = 1; + repeated string subnet_masks = 2; + // router is the next-hop router in dotted-decimal format + string router = 3; + + message IPv4Route { + string destination = 1; + string mask = 2; + // router is the next-hop router in dotted-decimal format + string router = 3; + } + repeated IPv4Route included_routes = 4; + repeated IPv4Route excluded_routes = 5; + } + IPv4Settings ipv4_settings = 5; + + message IPv6Settings { + repeated string addrs = 1; + repeated uint32 prefix_lengths = 2; + + message IPv6Route { + string destination = 1; + uint32 prefix_length = 2; + // router is the address of the next-hop + string router = 3; + } + repeated IPv6Route included_routes = 3; + repeated IPv6Route excluded_routes = 4; + } + IPv6Settings ipv6_settings = 6; +} + +// NetworkSettingsResponse is the response from the manager to the tunnel for a +// NetworkSettingsRequest +message NetworkSettingsResponse { + bool success = 1; + string error_message = 2; +} + +// StartRequest is a request from the manager to start the tunnel. The tunnel replies with a +// StartResponse. +message StartRequest { + int32 tunnel_file_descriptor = 1; + string coder_url = 2; + string api_token = 3; +} + +message StartResponse { + bool success = 1; + string error_message = 2; +} + +// StopRequest is a request from the manager to stop the tunnel. The tunnel replies with a +// StopResponse. +message StopRequest {} + +// StopResponse is a response to stopping the tunnel. After sending this response, the tunnel closes +// its side of the bidirectional stream for writing. +message StopResponse { + bool success = 1; + string error_message = 2; +} diff --git a/Coder Desktop/ProtoTests/ProtoTests.swift b/Coder Desktop/ProtoTests/ProtoTests.swift new file mode 100644 index 0000000..b49d4ad --- /dev/null +++ b/Coder Desktop/ProtoTests/ProtoTests.swift @@ -0,0 +1,253 @@ +import Testing +import Foundation +@testable import Coder_Desktop + +@Suite(.timeLimit(.minutes(1))) +struct SenderReceiverTests { + let pipe = Pipe() + let dispatch: DispatchIO + let queue: DispatchQueue = .global(qos: .utility) + + init() { + self.dispatch = DispatchIO( + type: .stream, + fileDescriptor: pipe.fileHandleForReading.fileDescriptor, + queue: queue, + cleanupHandler: {error in print("cleanupHandler: \(error)")} + ) + } + + @Test func sendOne() async throws { + let s = Sender(writeFD: pipe.fileHandleForWriting) + let r = Receiver(dispatch: dispatch, queue: queue) + var msg = Vpn_TunnelMessage() + msg.log = Vpn_Log() + msg.log.message = "test log" + Task { + try await s.send(msg) + try await s.close() + } + var count = 0 + for try await got in try await r.messages() { + #expect(got.log.message == "test log") + count += 1 + } + #expect(count == 1) + } + + @Test func sendMany() async throws { + let s = Sender(writeFD: pipe.fileHandleForWriting) + let r = Receiver(dispatch: dispatch, queue: queue) + var msg = Vpn_ManagerMessage() + msg.networkSettings.errorMessage = "test error" + Task { + for _ in 0..<10 { + try await s.send(msg) + } + try await s.close() + } + var count = 0 + for try await got in try await r.messages() { + #expect(got.networkSettings.errorMessage == "test error") + count += 1 + } + #expect(count == 10) + } +} + +@Suite(.timeLimit(.minutes(1))) +struct HandshakerTests { + let pipeMT = Pipe() + let pipeTM = Pipe() + let dispatchT: DispatchIO + let dispatchM: DispatchIO + let queue: DispatchQueue = .global(qos: .utility) + + init() { + self.dispatchT = DispatchIO( + type: .stream, + fileDescriptor: pipeMT.fileHandleForReading.fileDescriptor, + queue: queue, + cleanupHandler: {error in print("cleanupHandler: \(error)")} + ) + self.dispatchM = DispatchIO( + type: .stream, + fileDescriptor: pipeTM.fileHandleForReading.fileDescriptor, + queue: queue, + cleanupHandler: {error in print("cleanupHandler: \(error)")} + ) + } + + @Test("Default versions") + func mainline() async throws { + let uutTun = Handshaker( + writeFD: pipeTM.fileHandleForWriting, dispatch: dispatchT, queue: queue, role: .tunnel) + let uutMgr = Handshaker( + writeFD: pipeMT.fileHandleForWriting, dispatch: dispatchM, queue: queue, role: .manager) + let taskTun = Task { + try await uutTun.handshake() + } + let taskMgr = Task { + try await uutMgr.handshake() + } + let versionTun = try await taskTun.value + #expect(versionTun == ProtoVersion(1, 0)) + let versionMgr = try await taskMgr.value + #expect(versionMgr == ProtoVersion(1, 0)) + } + + + struct versionCase : CustomStringConvertible { + let tun: [ProtoVersion] + let mgr: [ProtoVersion] + let result: ProtoVersion + + var description: String { + return "\(tun) vs \(mgr) -> \(result)" + } + } + + @Test("explicit versions", arguments: [ + versionCase( + tun: [ProtoVersion(1, 0)], + mgr: [ProtoVersion(1, 1)], + result: ProtoVersion(1,0)), + versionCase( + tun: [ProtoVersion(1, 1)], + mgr: [ProtoVersion(1, 7)], + result: ProtoVersion(1,1)), + versionCase( + tun: [ProtoVersion(1, 7), ProtoVersion(2, 1)], + mgr: [ProtoVersion(1, 7)], + result: ProtoVersion(1, 7)), + versionCase( + tun: [ProtoVersion(1, 7)], + mgr: [ProtoVersion(1, 7), ProtoVersion(2, 1)], + result: ProtoVersion(1, 7)), + versionCase( + tun: [ProtoVersion(1, 3), ProtoVersion(2, 1)], + mgr: [ProtoVersion(1, 7)], + result: ProtoVersion(1, 3)), + ]) + func explictVersions(tc: versionCase) async throws { + let uutTun = Handshaker( + writeFD: pipeTM.fileHandleForWriting, dispatch: dispatchT, queue: queue, role: .tunnel, + versions: tc.tun + ) + let uutMgr = Handshaker( + writeFD: pipeMT.fileHandleForWriting, dispatch: dispatchM, queue: queue, role: .manager, + versions: tc.mgr + ) + let taskTun = Task { + try await uutTun.handshake() + } + let taskMgr = Task { + try await uutMgr.handshake() + } + let versionTun = try await taskTun.value + #expect(versionTun == tc.result) + let versionMgr = try await taskMgr.value + #expect(versionMgr == tc.result) + } + + @Test + func incompatible() async throws { + let uutTun = Handshaker( + writeFD: pipeTM.fileHandleForWriting, dispatch: dispatchT, queue: queue, role: .tunnel, + versions: [ProtoVersion(1,8)] + ) + let uutMgr = Handshaker( + writeFD: pipeMT.fileHandleForWriting, dispatch: dispatchM, queue: queue, role: .manager, + versions: [ProtoVersion(2,8)] + ) + let taskTun = Task { + try await uutTun.handshake() + } + let taskMgr = Task { + try await uutMgr.handshake() + } + await #expect(throws: HandshakeError.self) { + try await taskTun.value + } + await #expect(throws: HandshakeError.self) { + try await taskMgr.value + } + } +} + +@Suite(.timeLimit(.minutes(1))) +struct OneSidedHandshakerTests { + let pipeMT = Pipe() + let pipeTM = Pipe() + let queue: DispatchQueue = .global(qos: .utility) + let dispatchT: DispatchIO + let uut: Handshaker + + init() { + self.dispatchT = DispatchIO( + type: .stream, + fileDescriptor: pipeMT.fileHandleForReading.fileDescriptor, + queue: queue, + cleanupHandler: {error in print("cleanupHandler: \(error)")} + ) + self.uut = Handshaker( + writeFD: pipeTM.fileHandleForWriting, dispatch: dispatchT, queue: queue, role: .tunnel + ) + } + + @Test() + func badPreamble() async throws { + let taskTun = Task { + try await uut.handshake() + } + pipeMT.fileHandleForWriting.write(Data("something manager 1.0\n".utf8)) + let tunHdr = try pipeTM.fileHandleForReading.readToEnd() + #expect(tunHdr == Data("codervpn tunnel 1.0\n".utf8)) + await #expect(throws: HandshakeError.self) { + try await taskTun.value + } + } + + @Test(.timeLimit(.minutes(1))) + func badRole() async throws { + let taskTun = Task { + try await uut.handshake() + } + pipeMT.fileHandleForWriting.write(Data("codervpn head-honcho 1.0\n".utf8)) + let tunHdr = try pipeTM.fileHandleForReading.readToEnd() + #expect(tunHdr == Data("codervpn tunnel 1.0\n".utf8)) + await #expect(throws: HandshakeError.self) { + try await taskTun.value + } + } + + @Test(.timeLimit(.minutes(1))) + func badVersion() async throws { + let taskTun = Task { + try await uut.handshake() + } + pipeMT.fileHandleForWriting.write(Data("codervpn manager one-dot-oh\n".utf8)) + let tunHdr = try pipeTM.fileHandleForReading.readToEnd() + #expect(tunHdr == Data("codervpn tunnel 1.0\n".utf8)) + await #expect(throws: HandshakeError.self) { + try await taskTun.value + } + } + + @Test(.timeLimit(.minutes(1))) + func mainline() async throws { + let taskTun = Task { + let v = try await uut.handshake() + // close our pipe so that `readToEnd()` below succeeds. + try pipeTM.fileHandleForWriting.close() + return v + } + pipeMT.fileHandleForWriting.write(Data("codervpn manager 1.0\n".utf8)) + let tunHdr = try pipeTM.fileHandleForReading.readToEnd() + #expect(tunHdr == Data("codervpn tunnel 1.0\n".utf8)) + + let v = try await taskTun.value + #expect(v == ProtoVersion(1,0)) + } +} + diff --git a/Coder Desktop/ProtoTests/SpeakerTests.swift b/Coder Desktop/ProtoTests/SpeakerTests.swift new file mode 100644 index 0000000..c4199ff --- /dev/null +++ b/Coder Desktop/ProtoTests/SpeakerTests.swift @@ -0,0 +1,136 @@ +import Testing +import Foundation +@testable import Coder_Desktop + +/// A concrete, test class for the abstract Speaker, which overrides the handlers to send things to +/// continuations we set in the test. +class TestTunnel: Speaker { + var msgHandler: CheckedContinuation? + override func handleMessage(_ msg: Vpn_ManagerMessage) { + msgHandler?.resume(returning: msg) + } + + var rpcHandler: CheckedContinuation, Error>? + override func handleRPC(_ req: RPCRequest) { + rpcHandler?.resume(returning: req) + } +} + +@Suite(.timeLimit(.minutes(1))) +struct SpeakerTests { + let pipeMT = Pipe() + let pipeTM = Pipe() + let uut: TestTunnel + let sender: Sender + let dispatch: DispatchIO + let receiver: Receiver + let handshaker: Handshaker + + init() { + let queue = DispatchQueue.global(qos: .utility) + self.uut = TestTunnel( + writeFD: pipeTM.fileHandleForWriting, + readFD: pipeMT.fileHandleForReading + ) + self.dispatch = DispatchIO( + type: .stream, + fileDescriptor: pipeTM.fileHandleForReading.fileDescriptor, + queue: queue, + cleanupHandler: {error in print("cleanupHandler: \(error)")} + ) + self.sender = Sender(writeFD: pipeMT.fileHandleForWriting) + self.receiver = Receiver(dispatch: dispatch, queue: queue) + self.handshaker = Handshaker( + writeFD: pipeMT.fileHandleForWriting, + dispatch: self.dispatch, queue: queue, + role: .manager) + } + + @Test func handshake() async throws { + async let v = handshaker.handshake() + try await uut.handshake() + #expect(try await v == ProtoVersion(1, 0)) + } + + @Test func handleSingleMessage() async throws { + async let readDone: () = try uut.readLoop() + + let got = try await withCheckedThrowingContinuation { continuation in + uut.msgHandler = continuation + Task { + var s = Vpn_ManagerMessage() + s.start = Vpn_StartRequest() + await #expect(throws: Never.self) { + try await sender.send(s) + } + } + } + #expect(got.msg == .start(Vpn_StartRequest())) + try await sender.close() + try await readDone + } + + @Test func handleRPC() async throws { + async let readDone: () = try uut.readLoop() + + let got = try await withCheckedThrowingContinuation { continuation in + uut.rpcHandler = continuation + Task { + var s = Vpn_ManagerMessage() + s.start = Vpn_StartRequest() + s.rpc = Vpn_RPC() + s.rpc.msgID = 33 + await #expect(throws: Never.self) { + try await sender.send(s) + } + } + } + #expect(got.msg.msg == .start(Vpn_StartRequest())) + #expect(got.msg.rpc.msgID == 33) + var reply = Vpn_TunnelMessage() + reply.start = Vpn_StartResponse() + reply.rpc.responseTo = 33 + try await got.sendReply(reply) + uut.closeWrite() + + var count = 0 + await #expect(throws: Never.self) { + for try await reply in try await receiver.messages() { + count += 1 + #expect(reply.rpc.responseTo == 33) + } + #expect(count == 1) + } + try await sender.close() + try await readDone + } + + @Test func sendRPCs() async throws { + async let readDone: () = try uut.readLoop() + + async let managerDone = Task { + var count = 0 + for try await req in try await receiver.messages() { + #expect(req.msg == .networkSettings(Vpn_NetworkSettingsRequest())) + try #require(req.rpc.msgID != 0) + var reply = Vpn_ManagerMessage() + reply.networkSettings = Vpn_NetworkSettingsResponse() + reply.networkSettings.errorMessage = "test \(count)" + reply.rpc.responseTo = req.rpc.msgID + try await sender.send(reply) + count += 1 + } + #expect(count == 2) + } + for i in 0..<2 { + var req = Vpn_TunnelMessage() + req.networkSettings = Vpn_NetworkSettingsRequest() + let got = try await uut.unaryRPC(req) + #expect(got.networkSettings.errorMessage == "test \(i)") + } + uut.closeWrite() + _ = await managerDone + try await sender.close() + try await readDone + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..736c77f --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +proto: + protoc --swift_out=. 'Coder Desktop/Proto/vpn.proto'