- Notifications
You must be signed in to change notification settings - Fork3
feat: pass agent updates to UI#35
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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -2,14 +2,12 @@ import NetworkExtension | ||
import os | ||
import SwiftUI | ||
import VPNLib | ||
@MainActor | ||
protocolVPNService:ObservableObject{ | ||
varstate:VPNServiceState{get} | ||
varagents:[UUID:Agent]{get} | ||
func start()async | ||
func stop()async | ||
func configureTunnelProviderProtocol(proto:NETunnelProviderProtocol?) | ||
} | ||
@@ -26,12 +24,9 @@ enum VPNServiceError: Error, Equatable { | ||
case internalError(String) | ||
case systemExtensionError(SystemExtensionState) | ||
case networkExtensionError(NetworkExtensionState) | ||
vardescription:String{ | ||
switchself{ | ||
caselet.internalError(description): | ||
"Internal Error:\(description)" | ||
caselet.systemExtensionError(state): | ||
@@ -47,6 +42,7 @@ final class CoderVPNService: NSObject, VPNService { | ||
varlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"vpn") | ||
lazyvarxpc:VPNXPCInterface=.init(vpn:self) | ||
varterminating=false | ||
varworkspaces:[UUID:String]=[:] | ||
@PublishedvartunnelState:VPNServiceState=.disabled | ||
@PublishedvarsysExtnState:SystemExtensionState=.uninstalled | ||
@@ -61,7 +57,7 @@ final class CoderVPNService: NSObject, VPNService { | ||
return tunnelState | ||
} | ||
@Publishedvaragents:[UUID:Agent]=[:] | ||
// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get | ||
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework | ||
@@ -74,6 +70,16 @@ final class CoderVPNService: NSObject, VPNService { | ||
Task{ | ||
awaitloadNetworkExtension() | ||
} | ||
NotificationCenter.default.addObserver( | ||
self, | ||
selector: #selector(vpnDidUpdate(_:)), | ||
name:.NEVPNStatusDidChange, | ||
object:nil | ||
) | ||
} | ||
deinit{ | ||
NotificationCenter.default.removeObserver(self) | ||
} | ||
func start()async{ | ||
@@ -84,16 +90,14 @@ final class CoderVPNService: NSObject, VPNService { | ||
return | ||
} | ||
awaitenableNetworkExtension() | ||
// this ping is somewhat load bearing since it causes xpc to init | ||
xpc.ping() | ||
logger.debug("network extension enabled") | ||
} | ||
func stop()async{ | ||
guard tunnelState==.connectedelse{return} | ||
awaitdisableNetworkExtension() | ||
logger.info("network extension stopped") | ||
} | ||
@@ -131,31 +135,97 @@ final class CoderVPNService: NSObject, VPNService { | ||
} | ||
func onExtensionPeerUpdate(_ data:Data){ | ||
logger.info("network extension peer update") | ||
do{ | ||
letmsg=tryVpn_PeerUpdate(serializedBytes: data) | ||
debugPrint(msg) | ||
applyPeerUpdate(with: msg) | ||
}catch{ | ||
logger.error("failed to decode peer update\(error)") | ||
} | ||
} | ||
func applyPeerUpdate(with update:Vpn_PeerUpdate){ | ||
// Delete agents | ||
update.deletedAgents | ||
.compactMap{UUID(uuidData: $0.id)} | ||
.forEach{ agentIDin | ||
agents[agentID]=nil | ||
} | ||
update.deletedWorkspaces | ||
.compactMap{UUID(uuidData: $0.id)} | ||
.forEach{ workspaceIDin | ||
workspaces[workspaceID]=nil | ||
for(id, agent)in agentswhere agent.wsID== workspaceID{ | ||
agents[id]=nil | ||
} | ||
} | ||
// Update workspaces | ||
forworkspaceProtoin update.upsertedWorkspaces{ | ||
iflet workspaceID=UUID(uuidData: workspaceProto.id){ | ||
workspaces[workspaceID]= workspaceProto.name | ||
} | ||
} | ||
foragentProtoin update.upsertedAgents{ | ||
guardlet agentID=UUID(uuidData: agentProto.id)else{ | ||
continue | ||
} | ||
guardlet workspaceID=UUID(uuidData: agentProto.workspaceID)else{ | ||
continue | ||
} | ||
letworkspaceName=workspaces[workspaceID]??"Unknown Workspace" | ||
letnewAgent=Agent( | ||
id: agentID, | ||
name: agentProto.name, | ||
// If last handshake was not within last five minutes, the agent is unhealthy | ||
status: agentProto.lastHandshake.date>Date.now.addingTimeInterval(-300)?.okay:.off, | ||
copyableDNS: agentProto.fqdn.first??"UNKNOWN", | ||
wsName: workspaceName, | ||
wsID: workspaceID | ||
) | ||
// An existing agent with the same name, belonging to the same workspace | ||
// is from a previous workspace build, and should be removed. | ||
agents | ||
.filter{ $0.value.name== agentProto.name && $0.value.wsID== workspaceID} | ||
.forEach{agents[$0.key]=nil} | ||
agents[agentID]= newAgent | ||
} | ||
} | ||
} | ||
extensionCoderVPNService{ | ||
@objcprivatefunc vpnDidUpdate(_ notification:Notification){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. macOS can tell us when the Network Extension changes state, including if there was an error that caused it to disconnect, e.g. NE crashes, | ||
guardlet connection= notification.objectas?NETunnelProviderSessionelse{ | ||
return | ||
} | ||
switch connection.status{ | ||
case.disconnected: | ||
if terminating{ | ||
NSApp.reply(toApplicationShouldTerminate:true) | ||
} | ||
connection.fetchLastDisconnectError{ errin | ||
self.tunnelState=iflet err{ | ||
.failed(.internalError(err.localizedDescription)) | ||
}else{ | ||
.disabled | ||
} | ||
} | ||
case.connecting: | ||
tunnelState=.connecting | ||
case.connected: | ||
tunnelState=.connected | ||
case.reasserting: | ||
tunnelState=.connecting | ||
case.disconnecting: | ||
tunnelState=.disconnecting | ||
case.invalid: | ||
tunnelState=.failed(.networkExtensionError(.unconfigured)) | ||
@unknowndefault: | ||
tunnelState=.disabled | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -12,3 +12,22 @@ final class Inspection<V> { | ||
} | ||
} | ||
} | ||
extension UUID { | ||
var uuidData: Data { | ||
withUnsafePointer(to: uuid) { | ||
Data(bytes: $0, count: MemoryLayout.size(ofValue: uuid)) | ||
} | ||
} | ||
init?(uuidData: Data) { | ||
guard uuidData.count == 16 else { | ||
return nil | ||
} | ||
var uuid: uuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Very funny: there's no fixed size arrays in Swift (probably cause of objc), so | ||
withUnsafeMutableBytes(of: &uuid) { | ||
$0.copyBytes(from: uuidData) | ||
} | ||
self.init(uuid: uuid) | ||
} | ||
} |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.