Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: add network extension manager#18

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 5 commits intomainfromethan/ne-manager
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
12 changes: 6 additions & 6 deletionsCoder Desktop/Coder Desktop.xcodeproj/project.pbxproj
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -832,7 +832,7 @@
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All@@ -851,7 +851,7 @@
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All@@ -869,7 +869,7 @@
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All@@ -887,7 +887,7 @@
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-DesktopUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand DownExpand Up@@ -1038,7 +1038,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPNLibTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All@@ -1055,7 +1055,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 4399GN35BJ;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET =15.0;
MACOSX_DEPLOYMENT_TARGET =14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop.VPNLibTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ struct PreviewClient: Client {
roles: []
)
} catch {
throwClientError.reqError(AFError.explicitlyCancelled)
throw .reqError(.explicitlyCancelled)
}
}
}
8 changes: 4 additions & 4 deletionsCoder Desktop/Coder Desktop/SDK/Client.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,7 +39,7 @@ struct CoderClient: Client {
case let .success(data):
return HTTPResponse(resp: out.response!, data: data, req: out.request)
case let .failure(error):
throwClientError.reqError(error)
throw .reqError(error)
}
}

Expand All@@ -58,7 +58,7 @@ struct CoderClient: Client {
case let .success(data):
return HTTPResponse(resp: out.response!, data: data, req: out.request)
case let .failure(error):
throwClientError.reqError(error)
throw .reqError(error)
}
}

Expand All@@ -71,9 +71,9 @@ struct CoderClient: Client {
method: resp.req?.httpMethod,
url: resp.req?.url
)
returnClientError.apiError(out)
return .apiError(out)
} catch {
returnClientError.unexpectedResponse(resp.data[...1024])
return .unexpectedResponse(resp.data[...1024])
}
}

Expand Down
2 changes: 1 addition & 1 deletionCoder Desktop/Coder Desktop/SDK/User.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@ extension CoderClient {
do {
return try CoderClient.decoder.decode(User.self, from: res.data)
} catch {
throwClientError.unexpectedResponse(res.data[...1024])
throw .unexpectedResponse(res.data[...1024])
}
}
}
Expand Down
2 changes: 1 addition & 1 deletionCoder Desktop/Coder Desktop/Views/LoginForm.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,7 +70,7 @@ struct LoginForm<C: Client, S: Session>: View {
loading = true
defer { loading = false }
let client = C(url: url, token: sessionToken)
dothrows(ClientError){
do {
_ = try await client.user("me")
} catch {
loginError = .failedAuth(error)
Expand Down
2 changes: 1 addition & 1 deletionCoder Desktop/Coder DesktopTests/Util.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,7 +68,7 @@ struct MockClient: Client {
struct MockErrorClient: Client {
init(url _: URL, token _: String?) {}
func user(_: String) async throws(ClientError) -> Coder_Desktop.User {
throwClientError.reqError(.explicitlyCancelled)
throw .reqError(.explicitlyCancelled)
}
}

Expand Down
193 changes: 190 additions & 3 deletionsCoder Desktop/VPN/Manager.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,16 +4,203 @@ import VPNLib

actor Manager {
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think this is worth unit testing, at least right now. We'd need to mock the PTP, the TunnelHandle, the validator, and eventually the XPC speaker, all for a relatively simple abstraction that's mostly error handling.

let ptp: PacketTunnelProvider
let cfg: ManagerConfig

var tunnelHandle: TunnelHandle?
var speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>?
let tunnelHandle: TunnelHandle
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
var readLoop: Task<Void, any Error>!
// 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) {
init(with: PacketTunnelProvider, cfg: ManagerConfig) async throws(ManagerError) {
ptp = with
self.cfg = cfg
#if arch(arm64)
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-arm64.dylib")
#elseif arch(x86_64)
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-amd64.dylib")
#else
fatalError("unknown architecture")
#endif
do {
try await download(src: dylibPath, dest: dest)
} catch {
throw .download(error)
}
do {
try SignatureValidator.validate(path: dest)
} catch {
throw .validation(error)
}
do {
try tunnelHandle = TunnelHandle(dylibPath: dest)
} catch {
throw .tunnelSetup(error)
}
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
writeFD: tunnelHandle.writeHandle,
readFD: tunnelHandle.readHandle
)
do {
try await speaker.handshake()
} catch {
throw .handshake(error)
}
readLoop = Task { try await run() }
}

func run() async throws {
do {
for try await m in speaker {
switch m {
case let .message(msg):
handleMessage(msg)
case let .RPC(rpc):
handleRPC(rpc)
}
}
} catch {
logger.error("tunnel read loop failed: \(error)")
try await tunnelHandle.close()
// TODO: Notify app over XPC
return
}
logger.info("tunnel read loop exited")
try await tunnelHandle.close()
// TODO: Notify app over XPC
}

func handleMessage(_ msg: Vpn_TunnelMessage) {
guard let msgType = msg.msg else {
logger.critical("received message with no type")
return
}
switch msgType {
case .peerUpdate:
{}() // TODO: Send over XPC
case let .log(logMsg):
writeVpnLog(logMsg)
case .networkSettings, .start, .stop:
logger.critical("received unexpected message: `\(String(describing: msgType))`")
}
}

func handleRPC(_ rpc: RPCRequest<Vpn_ManagerMessage, Vpn_TunnelMessage>) {
guard let msgType = rpc.msg.msg else {
logger.critical("received rpc with no type")
return
}
switch msgType {
case let .networkSettings(ns):
let neSettings = convertNetworkSettingsRequest(ns)
ptp.setTunnelNetworkSettings(neSettings)
case .log, .peerUpdate, .start, .stop:
logger.critical("received unexpected rpc: `\(String(describing: msgType))`")
}
}

// TODO: Call via XPC
func startVPN() async throws(ManagerError) {
logger.info("sending start rpc")
guard let tunFd = ptp.tunnelFileDescriptor else {
throw .noTunnelFileDescriptor
}
let resp: Vpn_TunnelMessage
do {
resp = try await speaker.unaryRPC(.with { msg in
msg.start = .with { req in
req.tunnelFileDescriptor = tunFd
req.apiToken = cfg.apiToken
req.coderURL = cfg.serverUrl.absoluteString
}
})
} catch {
throw .failedRPC(error)
}
guard case let .start(startResp) = resp.msg else {
throw .incorrectResponse(resp)
}
if !startResp.success {
throw .errorResponse(msg: startResp.errorMessage)
}
// TODO: notify app over XPC
}

// TODO: Call via XPC
func stopVPN() async throws(ManagerError) {
logger.info("sending stop rpc")
let resp: Vpn_TunnelMessage
do {
resp = try await speaker.unaryRPC(.with { msg in
msg.stop = .init()
})
} catch {
throw .failedRPC(error)
}
guard case let .stop(stopResp) = resp.msg else {
throw .incorrectResponse(resp)
}
if !stopResp.success {
throw .errorResponse(msg: stopResp.errorMessage)
}
// TODO: notify app over XPC
}

// TODO: Call via XPC
// Retrieves the current state of all peers,
// as required when starting the app whilst the network extension is already running
func getPeerInfo() async throws(ManagerError) {
logger.info("sending peer state request")
let resp: Vpn_TunnelMessage
do {
resp = try await speaker.unaryRPC(.with { msg in
msg.getPeerUpdate = .init()
})
} catch {
throw .failedRPC(error)
}
guard case .peerUpdate = resp.msg else {
throw .incorrectResponse(resp)
}
// TODO: pass to app over XPC
}
}

public struct ManagerConfig {
let apiToken: String
let serverUrl: URL
}

enum ManagerError: Error {
case download(DownloadError)
case tunnelSetup(TunnelHandleError)
case handshake(HandshakeError)
case validation(ValidationError)
case incorrectResponse(Vpn_TunnelMessage)
case failedRPC(any Error)
case errorResponse(msg: String)
case noTunnelFileDescriptor
}

func writeVpnLog(_ log: Vpn_Log) {
let level: OSLogType = switch log.level {
case .info: .info
case .debug: .debug
// warn == error
case .warn: .error
case .error: .error
// critical == fatal == fault
case .critical: .fault
case .fatal: .fault
case .UNRECOGNIZED: .info
}
let logger = Logger(
subsystem: "\(Bundle.main.bundleIdentifier!).dylib",
category: log.loggerNames.joined(separator: ".")
)
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
logger.log(level: level, "\(log.message): \(fields)")
}
12 changes: 9 additions & 3 deletionsCoder Desktop/VPN/PacketTunnelProvider.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,10 +5,10 @@ import os
let CTLIOCGINFO: UInt = 0xC064_4E03

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

private var tunnelFileDescriptor: Int32? {
public var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
Expand DownExpand Up@@ -46,7 +46,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
completionHandler(nil)
return
}
manager = Manager(with: self)
Task {
// TODO: Retrieve access URL & Token via Keychain
manager = try await Manager(
with: self,
cfg: .init(apiToken: "fake-token", serverUrl: .init(string: "https://dev.coder.com")!)
)
}
completionHandler(nil)
}

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp