Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add dylib downloader and validator #16

Merged
merged 17 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 304 additions & 75 deletions Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "aa8dd97dc6e28dedc4a5c45c435467a247486474bf3c1caf5e67085d52325132",
"originHash" : "1cd4f7368eeddbaa35ef829e13093bc7081a4e6d3da9492d22db0757464ad473",
"pins" : [
{
"identity" : "alamofire",
Expand Down Expand Up @@ -27,6 +27,15 @@
"revision" : "e0c7eebc5a4465a3c4680764f26b7a61f567cdaf"
}
},
{
"identity" : "mocker",
"kind" : "remoteSourceControl",
"location" : "https://github.com/WeTransfer/Mocker",
"state" : {
"revision" : "95fa785c751f6bc40c49e112d433c3acf8417a97",
"version" : "3.0.2"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,20 @@
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "961679D82D030E1D00B2B6DF"
BuildableName = "ProtoTests.xctest"
BlueprintName = "ProtoTests"
BlueprintIdentifier = "AA3B3DA02D2D23860099996A"
BuildableName = "VPNLib.framework"
BlueprintName = "VPNLib"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AA3B3DA72D2D23860099996A"
BuildableName = "VPNLibTests.xctest"
BlueprintName = "VPNLibTests"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</TestableReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,29 @@
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9616792F2CFF117300B2B6DF"
BuildableName = "com.coder.Coder-Desktop.VPN.systemextension"
BlueprintName = "VPN"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "961679D82D030E1D00B2B6DF"
BuildableName = "ProtoTests.xctest"
BlueprintName = "ProtoTests"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -37,16 +40,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "961678FB2CFF100D00B2B6DF"
BuildableName = "Coder Desktop.app"
BlueprintName = "Coder Desktop"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All @@ -57,9 +50,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "961678FB2CFF100D00B2B6DF"
BuildableName = "Coder Desktop.app"
BlueprintName = "Coder Desktop"
BlueprintIdentifier = "9616792F2CFF117300B2B6DF"
BuildableName = "com.coder.Coder-Desktop.VPN.systemextension"
BlueprintName = "VPN"
ReferencedContainer = "container:Coder Desktop.xcodeproj">
</BuildableReference>
</MacroExpansion>
Expand Down
8 changes: 4 additions & 4 deletions Coder Desktop/Coder Desktop.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
{
"target" : {
"containerPath" : "container:Coder Desktop.xcodeproj",
"identifier" : "961679D82D030E1D00B2B6DF",
"name" : "ProtoTests"
"identifier" : "9616790E2CFF100E00B2B6DF",
"name" : "Coder DesktopTests"
}
},
{
"target" : {
"containerPath" : "container:Coder Desktop.xcodeproj",
"identifier" : "9616790E2CFF100E00B2B6DF",
"name" : "Coder DesktopTests"
"identifier" : "AA3B3DA72D2D23860099996A",
"name" : "VPNLibTests"
}
},
{
Expand Down
6 changes: 3 additions & 3 deletions Coder Desktop/Coder Desktop/SDK/Client.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Alamofire
import Foundation

protocol Client {
protocol Client: Sendable {
init(url: URL, token: String?)
func user(_ ident: String) async throws(ClientError) -> User
}
Expand Down Expand Up @@ -114,10 +114,10 @@ struct APIError: Decodable {
struct Response: Decodable {
let message: String
let detail: String?
let validations: [ValidationError]?
let validations: [FieldValidation]?
}

struct ValidationError: Decodable {
struct FieldValidation: Decodable {
let field: String
let detail: String
}
Expand Down
19 changes: 19 additions & 0 deletions Coder Desktop/VPN/Manager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import NetworkExtension
import os
import VPNLib

actor Manager {
let ptp: PacketTunnelProvider

var tunnelHandle: TunnelHandle?
var speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>?
// TODO: XPC Speaker

private let dest = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
.first!.appending(path: "coder-vpn.dylib")
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")

init(with: PacketTunnelProvider) {
ptp = with
}
}
58 changes: 54 additions & 4 deletions Coder Desktop/VPN/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,62 @@
import NetworkExtension
import os

class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options _: [String: NSObject]?, completionHandler _: @escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
/* From <sys/kern_control.h> */
let CTLIOCGINFO: UInt = 0xC064_4E03

class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network-extension")
private var manager: Manager?

private var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
_ = strcpy($0, "com.apple.net.utun_control")
}
}
for fd: Int32 in 0 ... 1024 {
var addr = sockaddr_ctl()
var ret: Int32 = -1
var len = socklen_t(MemoryLayout.size(ofValue: addr))
withUnsafeMutablePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
ret = getpeername(fd, $0, &len)
}
}
if ret != 0 || addr.sc_family != AF_SYSTEM {
continue
}
if ctlInfo.ctl_id == 0 {
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
if ret != 0 {
continue
}
}
if addr.sc_id == ctlInfo.ctl_id {
return fd
}
}
return nil
}

override func startTunnel(options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
guard manager == nil else {
logger.error("startTunnel called with non-nil Manager")
completionHandler(nil)
return
}
manager = Manager(with: self)
completionHandler(nil)
}

override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
guard manager == nil else {
logger.error("stopTunnel called with nil Manager")
completionHandler()
return
}
manager = nil
completionHandler()
}

Expand Down
91 changes: 91 additions & 0 deletions Coder Desktop/VPN/TunnelHandle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Foundation
import os

let startSymbol = "OpenTunnel"

actor TunnelHandle {
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "tunnel-handle")

private let tunnelWritePipe: Pipe
private let tunnelReadPipe: Pipe
private let dylibHandle: UnsafeMutableRawPointer

var writeHandle: FileHandle { tunnelReadPipe.fileHandleForWriting }
var readHandle: FileHandle { tunnelWritePipe.fileHandleForReading }

init(dylibPath: URL) throws(TunnelHandleError) {
guard let dylibHandle = dlopen(dylibPath.path, RTLD_NOW | RTLD_LOCAL) else {
throw .dylib(dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN")
}
self.dylibHandle = dylibHandle

guard let startSym = dlsym(dylibHandle, startSymbol) else {
throw .symbol(startSymbol, dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN")
}
let openTunnelFn = unsafeBitCast(startSym, to: OpenTunnel.self)
tunnelReadPipe = Pipe()
tunnelWritePipe = Pipe()
let res = openTunnelFn(tunnelReadPipe.fileHandleForReading.fileDescriptor,
tunnelWritePipe.fileHandleForWriting.fileDescriptor)
guard res == 0 else {
throw .openTunnel(OpenTunnelError(rawValue: res) ?? .unknown)
}
}

// This could be an isolated deinit in Swift 6.1
func close() throws(TunnelHandleError) {
var errs: [Error] = []
if dlclose(dylibHandle) == 0 {
errs.append(TunnelHandleError.dylib(dlerror().flatMap { String(cString: $0) } ?? "UNKNOWN"))
}
do {
try writeHandle.close()
} catch {
errs.append(error)
}
do {
try readHandle.close()
} catch {
errs.append(error)
}
if !errs.isEmpty {
throw .close(errs)
}
}
}

enum TunnelHandleError: Error {
case dylib(String)
case symbol(String, String)
case openTunnel(OpenTunnelError)
case pipe(any Error)
case close([any Error])

var description: String {
switch self {
case let .pipe(err): return "pipe error: \(err)"
case let .dylib(d): return d
case let .symbol(symbol, message): return "\(symbol): \(message)"
case let .openTunnel(error): return "OpenTunnel: \(error.message)"
case let .close(errs): return "close tunnel: \(errs.map(\.localizedDescription).joined(separator: ", "))"
}
}
}

enum OpenTunnelError: Int32 {
case errDupReadFD = -2
case errDupWriteFD = -3
case errOpenPipe = -4
case errNewTunnel = -5
case unknown = -99

var message: String {
switch self {
case .errDupReadFD: return "Failed to duplicate read file descriptor"
case .errDupWriteFD: return "Failed to duplicate write file descriptor"
case .errOpenPipe: return "Failed to open the pipe"
case .errNewTunnel: return "Failed to create a new tunnel"
case .unknown: return "Unknown error code"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef CoderPacketTunnelProvider_Bridging_Header_h
#define CoderPacketTunnelProvider_Bridging_Header_h

// GoInt32 OpenTunnel(GoInt32 cReadFD, GoInt32 cWriteFD);
typedef int(*OpenTunnel)(int, int);

#endif /* CoderPacketTunnelProvider_Bridging_Header_h */
Loading