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

Commitdf3d755

Browse files
chore: support operating the VPN without the app (#36)
1 parent10c2109 commitdf3d755

File tree

9 files changed

+81
-50
lines changed

9 files changed

+81
-50
lines changed

‎Coder Desktop/Coder Desktop/Coder_DesktopApp.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4747
}
4848
}
4949

50+
// This function MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
51+
// or return `.terminateNow`
5052
func applicationShouldTerminate(_:NSApplication)->NSApplication.TerminateReply{
53+
if !settings.stopVPNOnQuit{return.terminateNow}
5154
Task{
52-
await vpn.quit()
55+
await vpn.stop()
56+
NSApp.reply(toApplicationShouldTerminate:true)
5357
}
5458
return.terminateLater
5559
}

‎Coder Desktop/Coder Desktop/NetworkExtension.swift

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ enum NetworkExtensionState: Equatable {
2424
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
2525
/// NetworkExtension APIs.
2626
extensionCoderVPNService{
27-
// Updates the UI if a previous configuration exists
28-
func loadNetworkExtension()async{
27+
func hasNetworkExtensionConfig()async->Bool{
2928
do{
30-
tryawaitgetTunnelManager()
31-
neState=.disabled
29+
_=tryawaitgetTunnelManager()
30+
returntrue
3231
}catch{
33-
neState=.unconfigured
32+
returnfalse
3433
}
3534
}
3635

@@ -71,37 +70,29 @@ extension CoderVPNService {
7170
}
7271
}
7372

74-
funcenableNetworkExtension()async{
73+
funcstartTunnel()async{
7574
do{
7675
lettm=tryawaitgetTunnelManager()
77-
if !tm.isEnabled{
78-
tm.isEnabled=true
79-
tryawait tm.saveToPreferences()
80-
logger.debug("saved tunnel with enabled=true")
81-
}
8276
try tm.connection.startVPNTunnel()
8377
}catch{
84-
logger.error("enable network extension:\(error)")
78+
logger.error("start tunnel:\(error)")
8579
neState=.failed(error.localizedDescription)
8680
return
8781
}
88-
logger.debug("enabled andstarted tunnel")
82+
logger.debug("started tunnel")
8983
neState=.enabled
9084
}
9185

92-
funcdisableNetworkExtension()async{
86+
funcstopTunnel()async{
9387
do{
9488
lettm=tryawaitgetTunnelManager()
9589
tm.connection.stopVPNTunnel()
96-
tm.isEnabled=false
97-
98-
tryawait tm.saveToPreferences()
9990
}catch{
100-
logger.error("disable network extension:\(error)")
91+
logger.error("stop tunnel:\(error)")
10192
neState=.failed(error.localizedDescription)
10293
return
10394
}
104-
logger.debug("saved tunnel with enabled=false")
95+
logger.debug("stopped tunnel")
10596
neState=.disabled
10697
}
10798

‎Coder Desktop/Coder Desktop/State.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class Settings: ObservableObject {
104104
}
105105
}
106106

107+
@AppStorage(Keys.stopVPNOnQuit)varstopVPNOnQuit=true
108+
107109
init(store:UserDefaults=.standard){
108110
self.store= store
109111
_literalHeaders=Published(
@@ -116,6 +118,7 @@ class Settings: ObservableObject {
116118
enumKeys{
117119
staticletuseLiteralHeaders="UseLiteralHeaders"
118120
staticletliteralHeaders="LiteralHeaders"
121+
staticletstopVPNOnQuit="StopVPNOnQuit"
119122
}
120123
}
121124

‎Coder Desktop/Coder Desktop/VPNService.swift

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ enum VPNServiceError: Error, Equatable {
4141
finalclassCoderVPNService:NSObject,VPNService{
4242
varlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"vpn")
4343
lazyvarxpc:VPNXPCInterface=.init(vpn:self)
44-
varterminating=false
4544
varworkspaces:[UUID:String]=[:]
4645

4746
@PublishedvartunnelState:VPNServiceState=.disabled
@@ -68,8 +67,14 @@ final class CoderVPNService: NSObject, VPNService {
6867
super.init()
6968
installSystemExtension()
7069
Task{
71-
awaitloadNetworkExtension()
70+
neState=ifawaithasNetworkExtensionConfig(){
71+
.disabled
72+
}else{
73+
.unconfigured
74+
}
7275
}
76+
xpc.connect()
77+
xpc.getPeerState()
7378
NotificationCenter.default.addObserver(
7479
self,
7580
selector: #selector(vpnDidUpdate(_:)),
@@ -82,6 +87,11 @@ final class CoderVPNService: NSObject, VPNService {
8287
NotificationCenter.default.removeObserver(self)
8388
}
8489

90+
func clearPeers(){
91+
agents=[:]
92+
workspaces=[:]
93+
}
94+
8595
func start()async{
8696
switch tunnelState{
8797
case.disabled,.failed:
@@ -90,31 +100,18 @@ final class CoderVPNService: NSObject, VPNService {
90100
return
91101
}
92102

93-
awaitenableNetworkExtension()
94-
// this ping is somewhat load bearing since it causesxpc to init
103+
awaitstartTunnel()
104+
xpc.connect()
95105
xpc.ping()
96106
logger.debug("network extension enabled")
97107
}
98108

99109
func stop()async{
100110
guard tunnelState==.connectedelse{return}
101-
awaitdisableNetworkExtension()
111+
awaitstopTunnel()
102112
logger.info("network extension stopped")
103113
}
104114

105-
// Instructs the service to stop the VPN and then quit once the stop event
106-
// is read over XPC.
107-
// MUST only be called from `NSApplicationDelegate.applicationShouldTerminate`
108-
// MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
109-
func quit()async{
110-
guard tunnelState==.connectedelse{
111-
NSApp.reply(toApplicationShouldTerminate:true)
112-
return
113-
}
114-
terminating=true
115-
awaitstop()
116-
}
117-
118115
func configureTunnelProviderProtocol(proto:NETunnelProviderProtocol?){
119116
Task{
120117
iflet proto{
@@ -145,6 +142,22 @@ final class CoderVPNService: NSObject, VPNService {
145142
}
146143
}
147144

145+
func onExtensionPeerState(_ data:Data?){
146+
guardlet dataelse{
147+
logger.error("could not retrieve peer state from network extension, it may not be running")
148+
return
149+
}
150+
logger.info("received network extension peer state")
151+
do{
152+
letmsg=tryVpn_PeerUpdate(serializedBytes: data)
153+
debugPrint(msg)
154+
clearPeers()
155+
applyPeerUpdate(with: msg)
156+
}catch{
157+
logger.error("failed to decode peer update\(error)")
158+
}
159+
}
160+
148161
func applyPeerUpdate(with update:Vpn_PeerUpdate){
149162
// Delete agents
150163
update.deletedAgents
@@ -204,9 +217,6 @@ extension CoderVPNService {
204217
}
205218
switch connection.status{
206219
case.disconnected:
207-
if terminating{
208-
NSApp.reply(toApplicationShouldTerminate:true)
209-
}
210220
connection.fetchLastDisconnectError{ errin
211221
self.tunnelState=iflet err{
212222
.failed(.internalError(err.localizedDescription))

‎Coder Desktop/Coder Desktop/Views/Settings/GeneralTab.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ import LaunchAtLogin
22
import SwiftUI
33

44
structGeneralTab:View{
5+
@EnvironmentObjectvarsettings:Settings
56
varbody:someView{
67
Form{
78
Section{
89
LaunchAtLogin.Toggle("Launch at Login")
910
}
11+
Section{
12+
Toggle(isOn: $settings.stopVPNOnQuit){
13+
Text("Stop VPN on Quit")
14+
}
15+
}
1016
}.formStyle(.grouped)
1117
}
1218
}

‎Coder Desktop/Coder Desktop/XPCInterface.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import VPNLib
66
@objcfinalclassVPNXPCInterface:NSObject,VPNXPCClientCallbackProtocol,@uncheckedSendable{
77
privatevarsvc:CoderVPNService
88
privateletlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"VPNXPCInterface")
9-
privateletxpc:VPNXPCProtocol
9+
privatevarxpc:VPNXPCProtocol?
1010

1111
init(vpn:CoderVPNService){
1212
svc= vpn
13+
super.init()
14+
}
1315

16+
func connect(){
17+
guard xpc==nilelse{
18+
return
19+
}
1420
letnetworkExtDict=Bundle.main.object(forInfoDictionaryKey:"NetworkExtension")as?[String:Any]
1521
letmachServiceName=networkExtDict?["NEMachServiceName"]as?String
1622
letxpcConn=NSXPCConnection(machServiceName: machServiceName!)
@@ -21,30 +27,38 @@ import VPNLib
2127
}
2228
xpc= proxy
2329

24-
super.init()
25-
2630
xpcConn.exportedObject=self
2731
xpcConn.invalidationHandler={[logger]in
2832
Task{@MainActorin
2933
logger.error("XPC connection invalidated.")
34+
self.xpc=nil
3035
}
3136
}
3237
xpcConn.interruptionHandler={[logger]in
3338
Task{@MainActorin
3439
logger.error("XPC connection interrupted.")
40+
self.xpc=nil
3541
}
3642
}
3743
xpcConn.resume()
3844
}
3945

4046
func ping(){
41-
xpc.ping{
47+
xpc?.ping{
4248
Task{@MainActorin
4349
self.logger.info("Connected to NE over XPC")
4450
}
4551
}
4652
}
4753

54+
func getPeerState(){
55+
xpc?.getPeerState{ datain
56+
Task{@MainActorin
57+
self.svc.onExtensionPeerState(data)
58+
}
59+
}
60+
}
61+
4862
func onPeerUpdate(_ data:Data){
4963
Task{@MainActorin
5064
svc.onExtensionPeerUpdate(data)

‎Coder Desktop/VPN/Manager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ actor Manager {
194194

195195
// Retrieves the current state of all peers,
196196
// as required when starting the app whilst the network extension is already running
197-
funcgetPeerInfo()asyncthrows(ManagerError)->Vpn_PeerUpdate{
197+
funcgetPeerState()asyncthrows(ManagerError)->Vpn_PeerUpdate{
198198
logger.info("sending peer state request")
199199
letresp:Vpn_TunnelMessage
200200
do{

‎Coder Desktop/VPN/XPCInterface.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ import VPNLib
2020
}
2121
}
2222

23-
func getPeerInfo(with reply:@escaping()->Void){
24-
// TODO: Retrieve from Manager
25-
reply()
23+
func getPeerState(with reply:@escaping(Data?)->Void){
24+
letreply=CallbackWrapper(reply)
25+
Task{
26+
letdata=try?await manager?.getPeerState().serializedData()
27+
reply(data)
28+
}
2629
}
2730

2831
func ping(with reply:@escaping()->Void){

‎Coder Desktop/VPNLib/XPC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
@preconcurrency
44
@objcpublicprotocolVPNXPCProtocol{
5-
funcgetPeerInfo(with reply:@escaping()->Void)
5+
funcgetPeerState(with reply:@escaping(Data?)->Void)
66
func ping(with reply:@escaping()->Void)
77
}
88

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp