Skip to content

Commit d9255bc

Browse files
committed
chore: use slim binary over dylib
1 parent e6a3578 commit d9255bc

File tree

13 files changed

+521
-245
lines changed

13 files changed

+521
-245
lines changed

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 88 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,54 @@ actor Manager {
77
let cfg: ManagerConfig
88
let telemetryEnricher: TelemetryEnricher
99

10-
let tunnelHandle: TunnelHandle
10+
let tunnelDaemon: TunnelDaemon
1111
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
1212
var readLoop: Task<Void, any Error>!
1313

14-
// /var/root/Downloads
15-
private let dest = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)
16-
.first!.appending(path: "coder-vpn.dylib")
14+
#if arch(arm64)
15+
private static let binaryName = "coder-darwin-arm64"
16+
#else
17+
private static let binaryName = "coder-darwin-amd64"
18+
#endif
19+
20+
// /var/root/Library/Application Support/com.coder.Coder-Desktop/coder-darwin-{arm64,amd64}
21+
private let dest = try? FileManager.default
22+
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
23+
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.coder.Coder-Desktop", isDirectory: true)
24+
.appendingPathComponent(binaryName)
25+
1726
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager")
1827

1928
// swiftlint:disable:next function_body_length
2029
init(cfg: ManagerConfig) async throws(ManagerError) {
2130
self.cfg = cfg
2231
telemetryEnricher = TelemetryEnricher()
23-
#if arch(arm64)
24-
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-arm64.dylib")
25-
#elseif arch(x86_64)
26-
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-amd64.dylib")
27-
#else
28-
fatalError("unknown architecture")
29-
#endif
32+
guard let dest else {
33+
// This should never happen
34+
throw .fileError("Failed to create path for binary destination" +
35+
"(/var/root/Library/Application Support/com.coder.Coder-Desktop)")
36+
}
37+
do {
38+
try FileManager.default.ensureDirectories(for: dest)
39+
} catch {
40+
throw .fileError("Failed to create directories for binary destination: \(error.localizedDescription)")
41+
}
42+
let client = Client(url: cfg.serverUrl)
43+
let buildInfo: BuildInfoResponse
44+
do {
45+
buildInfo = try await client.buildInfo()
46+
} catch {
47+
throw .serverInfo(error.description)
48+
}
49+
guard let serverSemver = buildInfo.semver else {
50+
throw .serverInfo("invalid version: \(buildInfo.version)")
51+
}
52+
guard Validator.minimumCoderVersion
53+
.compare(serverSemver, options: .numeric) != .orderedDescending
54+
else {
55+
throw .belowMinimumCoderVersion(actualVersion: serverSemver)
56+
}
57+
let binaryPath = cfg.serverUrl.appending(path: "bin").appending(path: Manager.binaryName)
3058
do {
3159
let sessionConfig = URLSessionConfiguration.default
3260
// The tunnel might be asked to start before the network interfaces have woken up from sleep
@@ -35,7 +63,7 @@ actor Manager {
3563
sessionConfig.timeoutIntervalForRequest = 60
3664
sessionConfig.timeoutIntervalForResource = 300
3765
try await download(
38-
src: dylibPath,
66+
src: binaryPath,
3967
dest: dest,
4068
urlSession: URLSession(configuration: sessionConfig)
4169
) { progress in
@@ -45,48 +73,46 @@ actor Manager {
4573
throw .download(error)
4674
}
4775
pushProgress(stage: .validating)
48-
let client = Client(url: cfg.serverUrl)
49-
let buildInfo: BuildInfoResponse
5076
do {
51-
buildInfo = try await client.buildInfo()
77+
try Validator.validate(path: dest)
5278
} catch {
53-
throw .serverInfo(error.description)
54-
}
55-
guard let semver = buildInfo.semver else {
56-
throw .serverInfo("invalid version: \(buildInfo.version)")
79+
// Cleanup unvalid binary
80+
try? FileManager.default.removeItem(at: dest)
81+
throw .validation(error)
5782
}
83+
84+
// Without this, the TUN fd isn't recognised as a socket in the
85+
// spawned process, and the tunnel fails to start.
5886
do {
59-
try Validator.validate(path: dest, expectedVersion: semver)
87+
try unsetCloseOnExec(fd: cfg.tunFd)
6088
} catch {
61-
throw .validation(error)
89+
throw .cloexec(error)
6290
}
6391

6492
do {
65-
try tunnelHandle = TunnelHandle(dylibPath: dest)
93+
try tunnelDaemon = await TunnelDaemon(binaryPath: dest) { err in
94+
Task { try? await NEXPCServerDelegate.cancelProvider(error:
95+
makeNSError(suffix: "TunnelDaemon", desc: "Tunnel daemon: \(err.description)")
96+
) }
97+
}
6698
} catch {
6799
throw .tunnelSetup(error)
68100
}
69101
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
70-
writeFD: tunnelHandle.writeHandle,
71-
readFD: tunnelHandle.readHandle
102+
writeFD: tunnelDaemon.writeHandle,
103+
readFD: tunnelDaemon.readHandle
72104
)
73105
do {
74106
try await speaker.handshake()
75107
} catch {
76108
throw .handshake(error)
77109
}
78-
do {
79-
try await tunnelHandle.openTunnelTask?.value
80-
} catch let error as TunnelHandleError {
81-
logger.error("failed to wait for dylib to open tunnel: \(error, privacy: .public) ")
82-
throw .tunnelSetup(error)
83-
} catch {
84-
fatalError("openTunnelTask must only throw TunnelHandleError")
85-
}
86110

87111
readLoop = Task { try await run() }
88112
}
89113

114+
deinit { logger.debug("manager deinit") }
115+
90116
func run() async throws {
91117
do {
92118
for try await m in speaker {
@@ -99,14 +125,14 @@ actor Manager {
99125
}
100126
} catch {
101127
logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)")
102-
try await tunnelHandle.close()
128+
try await tunnelDaemon.close()
103129
try await NEXPCServerDelegate.cancelProvider(error:
104130
makeNSError(suffix: "Manager", desc: "Tunnel read loop failed: \(error.localizedDescription)")
105131
)
106132
return
107133
}
108134
logger.info("tunnel read loop exited")
109-
try await tunnelHandle.close()
135+
try await tunnelDaemon.close()
110136
try await NEXPCServerDelegate.cancelProvider(error: nil)
111137
}
112138

@@ -204,6 +230,12 @@ actor Manager {
204230
if !stopResp.success {
205231
throw .errorResponse(msg: stopResp.errorMessage)
206232
}
233+
do {
234+
try await tunnelDaemon.close()
235+
} catch {
236+
throw .tunnelFail(error)
237+
}
238+
readLoop.cancel()
207239
}
208240

209241
// Retrieves the current state of all peers,
@@ -239,28 +271,32 @@ struct ManagerConfig {
239271

240272
enum ManagerError: Error {
241273
case download(DownloadError)
242-
case tunnelSetup(TunnelHandleError)
274+
case fileError(String)
275+
case tunnelSetup(TunnelDaemonError)
243276
case handshake(HandshakeError)
244277
case validation(ValidationError)
245278
case incorrectResponse(Vpn_TunnelMessage)
279+
case cloexec(POSIXError)
246280
case failedRPC(any Error)
247281
case serverInfo(String)
248282
case errorResponse(msg: String)
249-
case noTunnelFileDescriptor
250-
case noApp
251-
case permissionDenied
252283
case tunnelFail(any Error)
284+
case belowMinimumCoderVersion(actualVersion: String)
253285

254286
var description: String {
255287
switch self {
256288
case let .download(err):
257289
"Download error: \(err.localizedDescription)"
290+
case let .fileError(msg):
291+
msg
258292
case let .tunnelSetup(err):
259293
"Tunnel setup error: \(err.localizedDescription)"
260294
case let .handshake(err):
261295
"Handshake error: \(err.localizedDescription)"
262296
case let .validation(err):
263297
"Validation error: \(err.localizedDescription)"
298+
case let .cloexec(err):
299+
"Failed to mark TUN fd as non-cloexec: \(err.localizedDescription)"
264300
case .incorrectResponse:
265301
"Received unexpected response over tunnel"
266302
case let .failedRPC(err):
@@ -269,14 +305,13 @@ enum ManagerError: Error {
269305
msg
270306
case let .errorResponse(msg):
271307
msg
272-
case .noTunnelFileDescriptor:
273-
"Could not find a tunnel file descriptor"
274-
case .noApp:
275-
"The VPN must be started with the app open during first-time setup."
276-
case .permissionDenied:
277-
"Permission was not granted to execute the CoderVPN dylib"
278308
case let .tunnelFail(err):
279-
"Failed to communicate with dylib over tunnel: \(err.localizedDescription)"
309+
"Failed to communicate with daemon over tunnel: \(err.localizedDescription)"
310+
case let .belowMinimumCoderVersion(actualVersion):
311+
"""
312+
The Coder deployment must be version \(Validator.minimumCoderVersion)
313+
or higher to use Coder Desktop. Current version: \(actualVersion)
314+
"""
280315
}
281316
}
282317

@@ -297,9 +332,16 @@ func writeVpnLog(_ log: Vpn_Log) {
297332
case .UNRECOGNIZED: .info
298333
}
299334
let logger = Logger(
300-
subsystem: "\(Bundle.main.bundleIdentifier!).dylib",
335+
subsystem: "\(Bundle.main.bundleIdentifier!).daemon",
301336
category: log.loggerNames.joined(separator: ".")
302337
)
303338
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
304339
logger.log(level: level, "\(log.message, privacy: .public)\(fields.isEmpty ? "" : ": \(fields)", privacy: .public)")
305340
}
341+
342+
extension FileManager {
343+
func ensureDirectories(for url: URL) throws {
344+
let dir = url.hasDirectoryPath ? url : url.deletingLastPathComponent()
345+
try createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
346+
}
347+
}

Coder-Desktop/Coder-DesktopHelper/TunnelHandle.swift

Lines changed: 0 additions & 116 deletions
This file was deleted.

Coder-Desktop/Coder-DesktopHelper/com_coder_Coder_Desktop_VPN-Bridging-Header.h

Lines changed: 0 additions & 7 deletions
This file was deleted.

Coder-Desktop/Coder-DesktopTests/LoginFormTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ struct LoginTests {
134134
username: "admin"
135135
)
136136
let buildInfo = BuildInfoResponse(
137-
version: "v2.20.0"
137+
version: "v2.24.2"
138138
)
139139

140140
try Mock(

Coder-Desktop/VPNLib/Download.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ extension DownloadManager: URLSessionDownloadDelegate {
102102
return
103103
}
104104
guard httpResponse.statusCode != 304 else {
105-
// We already have the latest dylib downloaded in dest
105+
// We already have the latest binary downloaded in dest
106106
continuation.resume()
107107
return
108108
}

Coder-Desktop/VPNLib/Receiver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ actor Receiver<RecvMsg: Message> {
6969
},
7070
onCancel: {
7171
self.logger.debug("async stream canceled")
72-
self.dispatch.close()
72+
self.dispatch.close(flags: [.stop])
7373
}
7474
)
7575
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy