Skip to content

Commit 479576a

Browse files
committed
chore: use slim binary over dylib
1 parent 6687411 commit 479576a

File tree

10 files changed

+497
-219
lines changed

10 files changed

+497
-219
lines changed

Coder-Desktop/Coder-DesktopHelper/Manager.swift

Lines changed: 86 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 SignatureValidator.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,44 @@ 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 SignatureValidator.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+
throw .validation(error)
5780
}
81+
82+
// Without this, the TUN fd isn't recognised as a socket in the
83+
// spawned process, and the tunnel fails to start.
5884
do {
59-
try SignatureValidator.validate(path: dest, expectedVersion: semver)
85+
try unsetCloseOnExec(fd: cfg.tunFd)
6086
} catch {
61-
throw .validation(error)
87+
throw .cloexec(error)
6288
}
6389

6490
do {
65-
try tunnelHandle = TunnelHandle(dylibPath: dest)
91+
try tunnelDaemon = await TunnelDaemon(binaryPath: dest) { err in
92+
Task { try? await NEXPCServerDelegate.cancelProvider(error:
93+
makeNSError(suffix: "TunnelDaemon", desc: "Tunnel daemon: \(err.description)")
94+
) }
95+
}
6696
} catch {
6797
throw .tunnelSetup(error)
6898
}
6999
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
70-
writeFD: tunnelHandle.writeHandle,
71-
readFD: tunnelHandle.readHandle
100+
writeFD: tunnelDaemon.writeHandle,
101+
readFD: tunnelDaemon.readHandle
72102
)
73103
do {
74104
try await speaker.handshake()
75105
} catch {
76106
throw .handshake(error)
77107
}
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-
}
86108

87109
readLoop = Task { try await run() }
88110
}
89111

112+
deinit { logger.debug("manager deinit") }
113+
90114
func run() async throws {
91115
do {
92116
for try await m in speaker {
@@ -99,14 +123,14 @@ actor Manager {
99123
}
100124
} catch {
101125
logger.error("tunnel read loop failed: \(error.localizedDescription, privacy: .public)")
102-
try await tunnelHandle.close()
126+
try await tunnelDaemon.close()
103127
try await NEXPCServerDelegate.cancelProvider(error:
104128
makeNSError(suffix: "Manager", desc: "Tunnel read loop failed: \(error.localizedDescription)")
105129
)
106130
return
107131
}
108132
logger.info("tunnel read loop exited")
109-
try await tunnelHandle.close()
133+
try await tunnelDaemon.close()
110134
try await NEXPCServerDelegate.cancelProvider(error: nil)
111135
}
112136

@@ -204,6 +228,12 @@ actor Manager {
204228
if !stopResp.success {
205229
throw .errorResponse(msg: stopResp.errorMessage)
206230
}
231+
do {
232+
try await tunnelDaemon.close()
233+
} catch {
234+
throw .tunnelFail(error)
235+
}
236+
readLoop.cancel()
207237
}
208238

209239
// Retrieves the current state of all peers,
@@ -239,28 +269,32 @@ struct ManagerConfig {
239269

240270
enum ManagerError: Error {
241271
case download(DownloadError)
242-
case tunnelSetup(TunnelHandleError)
272+
case fileError(String)
273+
case tunnelSetup(TunnelDaemonError)
243274
case handshake(HandshakeError)
244275
case validation(ValidationError)
245276
case incorrectResponse(Vpn_TunnelMessage)
277+
case cloexec(POSIXError)
246278
case failedRPC(any Error)
247279
case serverInfo(String)
248280
case errorResponse(msg: String)
249-
case noTunnelFileDescriptor
250-
case noApp
251-
case permissionDenied
252281
case tunnelFail(any Error)
282+
case belowMinimumCoderVersion(actualVersion: String)
253283

254284
var description: String {
255285
switch self {
256286
case let .download(err):
257287
"Download error: \(err.localizedDescription)"
288+
case let .fileError(msg):
289+
msg
258290
case let .tunnelSetup(err):
259291
"Tunnel setup error: \(err.localizedDescription)"
260292
case let .handshake(err):
261293
"Handshake error: \(err.localizedDescription)"
262294
case let .validation(err):
263295
"Validation error: \(err.localizedDescription)"
296+
case let .cloexec(err):
297+
"Failed to mark TUN fd as non-cloexec: \(err.localizedDescription)"
264298
case .incorrectResponse:
265299
"Received unexpected response over tunnel"
266300
case let .failedRPC(err):
@@ -269,14 +303,13 @@ enum ManagerError: Error {
269303
msg
270304
case let .errorResponse(msg):
271305
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"
278306
case let .tunnelFail(err):
279-
"Failed to communicate with dylib over tunnel: \(err.localizedDescription)"
307+
"Failed to communicate with daemon over tunnel: \(err.localizedDescription)"
308+
case let .belowMinimumCoderVersion(actualVersion):
309+
"""
310+
The Coder deployment must be version \(SignatureValidator.minimumCoderVersion)
311+
or higher to use Coder Desktop. Current version: \(actualVersion)
312+
"""
280313
}
281314
}
282315

@@ -297,9 +330,16 @@ func writeVpnLog(_ log: Vpn_Log) {
297330
case .UNRECOGNIZED: .info
298331
}
299332
let logger = Logger(
300-
subsystem: "\(Bundle.main.bundleIdentifier!).dylib",
333+
subsystem: "\(Bundle.main.bundleIdentifier!).daemon",
301334
category: log.loggerNames.joined(separator: ".")
302335
)
303336
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
304337
logger.log(level: level, "\(log.message, privacy: .public)\(fields.isEmpty ? "" : ": \(fields)", privacy: .public)")
305338
}
339+
340+
extension FileManager {
341+
func ensureDirectories(for url: URL) throws {
342+
let dir = url.hasDirectoryPath ? url : url.deletingLastPathComponent()
343+
try createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
344+
}
345+
}

Coder-Desktop/Coder-DesktopHelper/TunnelHandle.swift

Lines changed: 0 additions & 116 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)