Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9e0b05b

Browse files
spikecurtisethanndickson
authored andcommittedJan 14, 2025·
feat: install and activate the tunnel provider as network extension
1 parent 46c2c09 commit 9e0b05b

14 files changed

+383
-28
lines changed
 

‎Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@
769769
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
770770
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
771771
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
772+
CODE_SIGN_IDENTITY = "Apple Development";
772773
CODE_SIGN_STYLE = Automatic;
773774
COMBINE_HIDPI_IMAGES = YES;
774775
CURRENT_PROJECT_VERSION = 1;
@@ -788,6 +789,7 @@
788789
MARKETING_VERSION = 1.0;
789790
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
790791
PRODUCT_NAME = "$(TARGET_NAME)";
792+
PROVISIONING_PROFILE_SPECIFIER = "";
791793
SWIFT_EMIT_LOC_STRINGS = YES;
792794
SWIFT_VERSION = 6.0;
793795
};
@@ -799,6 +801,7 @@
799801
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
800802
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
801803
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
804+
CODE_SIGN_IDENTITY = "Apple Development";
802805
CODE_SIGN_STYLE = Automatic;
803806
COMBINE_HIDPI_IMAGES = YES;
804807
CURRENT_PROJECT_VERSION = 1;
@@ -818,6 +821,7 @@
818821
MARKETING_VERSION = 1.0;
819822
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
820823
PRODUCT_NAME = "$(TARGET_NAME)";
824+
PROVISIONING_PROFILE_SPECIFIER = "";
821825
SWIFT_EMIT_LOC_STRINGS = YES;
822826
SWIFT_VERSION = 6.0;
823827
};
@@ -901,6 +905,7 @@
901905
isa = XCBuildConfiguration;
902906
buildSettings = {
903907
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
908+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
904909
CODE_SIGN_STYLE = Automatic;
905910
CURRENT_PROJECT_VERSION = 1;
906911
DEAD_CODE_STRIPPING = YES;
@@ -932,6 +937,7 @@
932937
isa = XCBuildConfiguration;
933938
buildSettings = {
934939
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
940+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
935941
CODE_SIGN_STYLE = Automatic;
936942
CURRENT_PROJECT_VERSION = 1;
937943
DEAD_CODE_STRIPPING = YES;

‎Coder Desktop/Coder Desktop/Coder_Desktop.entitlements

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<array>
77
<string>packet-tunnel-provider</string>
88
</array>
9+
<key>com.apple.developer.system-extension.install</key>
10+
<true/>
911
<key>com.apple.security.app-sandbox</key>
1012
<true/>
1113
<key>com.apple.security.files.user-selected.read-only</key>

‎Coder Desktop/Coder Desktop/Coder_DesktopApp.swift

+6-7
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct DesktopApp: App {
1111
EmptyView()
1212
}
1313
Window("Sign In", id: Windows.login.rawValue) {
14-
LoginForm<PreviewClient, PreviewSession>()
14+
LoginForm<CoderClient, SecureSession>()
1515
}.environmentObject(appDelegate.session)
1616
.windowResizability(.contentSize)
1717
}
@@ -20,18 +20,17 @@ struct DesktopApp: App {
2020
@MainActor
2121
class AppDelegate: NSObject, NSApplicationDelegate {
2222
private var menuBarExtra: FluidMenuBarExtra?
23-
let vpn: PreviewVPN
24-
let session: PreviewSession
23+
let vpn: CoderVPNService
24+
let session: SecureSession
2525

2626
override init() {
27-
// TODO: Replace with real implementations
28-
vpn = PreviewVPN()
29-
session = PreviewSession()
27+
vpn = CoderVPNService()
28+
session = SecureSession(onChange: vpn.configureTunnelProviderProtocol)
3029
}
3130

3231
func applicationDidFinishLaunching(_: Notification) {
3332
menuBarExtra = FluidMenuBarExtra(title: "Coder Desktop", image: "MenuBarIcon") {
34-
VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
33+
VPNMenu<CoderVPNService, SecureSession>().frame(width: 256)
3534
.environmentObject(self.vpn)
3635
.environmentObject(self.session)
3736
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import NetworkExtension
2+
import os
3+
4+
enum NetworkExtensionState: Equatable {
5+
case unconfigured
6+
case disbled
7+
case enabled
8+
case failed(String)
9+
10+
var description: String {
11+
switch self {
12+
case .unconfigured:
13+
return "Not logged in to Coder"
14+
case .enabled:
15+
return "NetworkExtension tunnel enabled"
16+
case .disbled:
17+
return "NetworkExtension tunnel disabled"
18+
case let .failed(error):
19+
return "NetworkExtension config failed: \(error)"
20+
}
21+
}
22+
}
23+
24+
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
25+
/// NetworkExtension APIs.
26+
extension CoderVPNService {
27+
func configureNetworkExtension(proto: NETunnelProviderProtocol) async {
28+
// removing the old tunnels, rather than reconfiguring ensures that configuration changes
29+
// are picked up.
30+
do {
31+
try await removeNetworkExtension()
32+
} catch {
33+
logger.error("remove tunnel failed: \(error)")
34+
neState = .failed(error.localizedDescription)
35+
return
36+
}
37+
logger.debug("inserting new tunnel")
38+
39+
let tm = NETunnelProviderManager()
40+
tm.localizedDescription = "CoderVPN"
41+
tm.protocolConfiguration = proto
42+
43+
logger.debug("saving new tunnel")
44+
do {
45+
try await tm.saveToPreferences()
46+
} catch {
47+
logger.error("save tunnel failed: \(error)")
48+
neState = .failed(error.localizedDescription)
49+
}
50+
}
51+
52+
func removeNetworkExtension() async throws(VPNServiceError) {
53+
do {
54+
let tunnels = try await NETunnelProviderManager.loadAllFromPreferences()
55+
for tunnel in tunnels {
56+
try await tunnel.removeFromPreferences()
57+
}
58+
} catch {
59+
throw VPNServiceError.internalError("couldn't remove tunnels: \(error)")
60+
}
61+
}
62+
63+
func enableNetworkExtension() async {
64+
do {
65+
let tm = try await getTunnelManager()
66+
if !tm.isEnabled {
67+
tm.isEnabled = true
68+
try await tm.saveToPreferences()
69+
logger.debug("saved tunnel with enabled=true")
70+
}
71+
try tm.connection.startVPNTunnel()
72+
} catch {
73+
logger.error("enable network extension: \(error)")
74+
neState = .failed(error.localizedDescription)
75+
return
76+
}
77+
logger.debug("enabled and started tunnel")
78+
neState = .enabled
79+
}
80+
81+
func disableNetworkExtension() async {
82+
do {
83+
let tm = try await getTunnelManager()
84+
tm.connection.stopVPNTunnel()
85+
tm.isEnabled = false
86+
87+
try await tm.saveToPreferences()
88+
} catch {
89+
logger.error("disable network extension: \(error)")
90+
neState = .failed(error.localizedDescription)
91+
return
92+
}
93+
logger.debug("saved tunnel with enabled=false")
94+
neState = .disbled
95+
}
96+
97+
private func getTunnelManager() async throws(VPNServiceError) -> NETunnelProviderManager {
98+
var tunnels: [NETunnelProviderManager] = []
99+
do {
100+
tunnels = try await NETunnelProviderManager.loadAllFromPreferences()
101+
} catch {
102+
throw VPNServiceError.internalError("couldn't load tunnels: \(error)")
103+
}
104+
if tunnels.isEmpty {
105+
throw VPNServiceError.internalError("no tunnels found")
106+
}
107+
return tunnels.first!
108+
}
109+
}
110+
111+
// we're going to mark NETunnelProviderManager as Sendable since there are official APIs that return
112+
// it async.
113+
extension NETunnelProviderManager: @unchecked @retroactive Sendable {}

‎Coder Desktop/Coder Desktop/Preview Content/PreviewSession.swift

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import NetworkExtension
12
import SwiftUI
23

34
class PreviewSession: Session {
@@ -21,4 +22,8 @@ class PreviewSession: Session {
2122
hasSession = false
2223
sessionToken = nil
2324
}
25+
26+
func tunnelProviderProtocol() -> NETunnelProviderProtocol? {
27+
return nil
28+
}
2429
}

‎Coder Desktop/Coder Desktop/Preview Content/PreviewVPN.swift

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import NetworkExtension
12
import SwiftUI
23

34
@MainActor
@@ -28,10 +29,10 @@ final class PreviewVPN: Coder_Desktop.VPNService {
2829
do {
2930
try await Task.sleep(for: .seconds(10))
3031
} catch {
31-
state = .failed(.exampleError)
32+
state = .failed(.longTestError)
3233
return
3334
}
34-
state = shouldFail ? .failed(.exampleError) : .connected
35+
state = shouldFail ? .failed(.longTestError) : .connected
3536
}
3637

3738
func stop() async {
@@ -40,9 +41,13 @@ final class PreviewVPN: Coder_Desktop.VPNService {
4041
do {
4142
try await Task.sleep(for: .seconds(10))
4243
} catch {
43-
state = .failed(.exampleError)
44+
state = .failed(.longTestError)
4445
return
4546
}
4647
state = .disabled
4748
}
49+
50+
func configureTunnelProviderProtocol(proto _: NETunnelProviderProtocol?) {
51+
state = .connecting
52+
}
4853
}

‎Coder Desktop/Coder Desktop/Session.swift

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import KeychainAccess
3+
import NetworkExtension
34

45
protocol Session: ObservableObject {
56
var hasSession: Bool { get }
@@ -8,9 +9,12 @@ protocol Session: ObservableObject {
89

910
func store(baseAccessURL: URL, sessionToken: String)
1011
func clear()
12+
func tunnelProviderProtocol() -> NETunnelProviderProtocol?
1113
}
1214

13-
class SecureSession: ObservableObject {
15+
class SecureSession: ObservableObject & Session {
16+
let appId = Bundle.main.bundleIdentifier!
17+
1418
// Stored in UserDefaults
1519
@Published private(set) var hasSession: Bool {
1620
didSet {
@@ -31,9 +35,21 @@ class SecureSession: ObservableObject {
3135
}
3236
}
3337

38+
func tunnelProviderProtocol() -> NETunnelProviderProtocol? {
39+
if !hasSession { return nil }
40+
let proto = NETunnelProviderProtocol()
41+
proto.providerBundleIdentifier = "\(appId).VPN"
42+
proto.passwordReference = keychain[attributes: Keys.sessionToken]?.persistentRef
43+
proto.serverAddress = baseAccessURL!.absoluteString
44+
return proto
45+
}
46+
3447
private let keychain: Keychain
3548

36-
public init() {
49+
let onChange: ((NETunnelProviderProtocol?) -> Void)?
50+
51+
public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil) {
52+
self.onChange = onChange
3753
keychain = Keychain(service: Bundle.main.bundleIdentifier!)
3854
_hasSession = Published(initialValue: UserDefaults.standard.bool(forKey: Keys.hasSession))
3955
_baseAccessURL = Published(initialValue: UserDefaults.standard.url(forKey: Keys.baseAccessURL))
@@ -46,11 +62,13 @@ class SecureSession: ObservableObject {
4662
hasSession = true
4763
self.baseAccessURL = baseAccessURL
4864
self.sessionToken = sessionToken
65+
if let onChange { onChange(tunnelProviderProtocol()) }
4966
}
5067

5168
public func clear() {
5269
hasSession = false
5370
sessionToken = nil
71+
if let onChange { onChange(tunnelProviderProtocol()) }
5472
}
5573

5674
private func keychainGet(for key: String) -> String? {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import Foundation
2+
import os
3+
import SystemExtensions
4+
5+
enum SystemExtensionState: Equatable, Sendable {
6+
case uninstalled
7+
case needsUserApproval
8+
case installed
9+
case failed(String)
10+
11+
var description: String {
12+
switch self {
13+
case .uninstalled:
14+
return "VPN SystemExtension is waiting to be activated"
15+
case .needsUserApproval:
16+
return "VPN SystemExtension needs user approval to activate"
17+
case .installed:
18+
return "VPN SystemExtension is installed"
19+
case let .failed(error):
20+
return "VPN SystemExtension failed with error: \(error)"
21+
}
22+
}
23+
}
24+
25+
protocol SystemExtensionAsyncRecorder: Sendable {
26+
func recordSystemExtensionState(_ state: SystemExtensionState) async
27+
}
28+
29+
extension CoderVPNService: SystemExtensionAsyncRecorder {
30+
func recordSystemExtensionState(_ state: SystemExtensionState) async {
31+
sysExtnState = state
32+
}
33+
34+
var extensionBundle: Bundle {
35+
let extensionsDirectoryURL = URL(
36+
fileURLWithPath: "Contents/Library/SystemExtensions",
37+
relativeTo: Bundle.main.bundleURL
38+
)
39+
let extensionURLs: [URL]
40+
do {
41+
extensionURLs = try FileManager.default.contentsOfDirectory(at: extensionsDirectoryURL,
42+
includingPropertiesForKeys: nil,
43+
options: .skipsHiddenFiles)
44+
} catch {
45+
fatalError("Failed to get the contents of " +
46+
"\(extensionsDirectoryURL.absoluteString): \(error.localizedDescription)")
47+
}
48+
49+
// here we're just going to assume that there is only ever going to be one SystemExtension
50+
// packaged up in the application bundle. If we ever need to ship multiple versions or have
51+
// multiple extensions, we'll need to revisit this assumption.
52+
guard let extensionURL = extensionURLs.first else {
53+
fatalError("Failed to find any system extensions")
54+
}
55+
56+
guard let extensionBundle = Bundle(url: extensionURL) else {
57+
fatalError("Failed to create a bundle with URL \(extensionURL.absoluteString)")
58+
}
59+
60+
return extensionBundle
61+
}
62+
63+
func installSystemExtension() {
64+
logger.info("activating SystemExtension")
65+
guard let bundleID = extensionBundle.bundleIdentifier else {
66+
logger.error("Bundle has no identifier")
67+
return
68+
}
69+
let request = OSSystemExtensionRequest.activationRequest(
70+
forExtensionWithIdentifier: bundleID,
71+
queue: .main
72+
)
73+
let delegate = SystemExtensionDelegate(asyncDelegate: self)
74+
request.delegate = delegate
75+
OSSystemExtensionManager.shared.submitRequest(request)
76+
logger.info("submitted SystemExtension request with bundleID: \(bundleID)")
77+
}
78+
}
79+
80+
/// A delegate for the OSSystemExtensionRequest that maps the callbacks to async calls on the
81+
/// AsyncDelegate (CoderVPNService in production).
82+
class SystemExtensionDelegate<AsyncDelegate: SystemExtensionAsyncRecorder>:
83+
NSObject, OSSystemExtensionRequestDelegate
84+
{
85+
private var logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "vpn-installer")
86+
private var asyncDelegate: AsyncDelegate
87+
88+
init(asyncDelegate: AsyncDelegate) {
89+
self.asyncDelegate = asyncDelegate
90+
logger.info("SystemExtensionDelegate initialized")
91+
}
92+
93+
func request(
94+
_: OSSystemExtensionRequest,
95+
didFinishWithResult result: OSSystemExtensionRequest.Result
96+
) {
97+
guard result == .completed else {
98+
logger.error("Unexpected result \(result.rawValue) for system extension request")
99+
let state = SystemExtensionState.failed("system extension not installed: \(result.rawValue)")
100+
Task { [asyncDelegate] in
101+
await asyncDelegate.recordSystemExtensionState(state)
102+
}
103+
return
104+
}
105+
logger.info("SystemExtension activated")
106+
Task { [asyncDelegate] in
107+
await asyncDelegate.recordSystemExtensionState(SystemExtensionState.installed)
108+
}
109+
}
110+
111+
func request(_: OSSystemExtensionRequest, didFailWithError error: Error) {
112+
logger.error("System extension request failed: \(error.localizedDescription)")
113+
Task { [asyncDelegate] in
114+
await asyncDelegate.recordSystemExtensionState(
115+
SystemExtensionState.failed(error.localizedDescription))
116+
}
117+
}
118+
119+
func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
120+
logger.error("Extension \(request.identifier) requires user approval")
121+
Task { [asyncDelegate] in
122+
await asyncDelegate.recordSystemExtensionState(SystemExtensionState.needsUserApproval)
123+
}
124+
}
125+
126+
func request(
127+
_ request: OSSystemExtensionRequest,
128+
actionForReplacingExtension existing: OSSystemExtensionProperties,
129+
withExtension extension: OSSystemExtensionProperties
130+
) -> OSSystemExtensionRequest.ReplacementAction {
131+
// swiftlint: disable line_length
132+
logger.info("Replacing \(request.identifier) v\(existing.bundleShortVersion) with v\(`extension`.bundleShortVersion)")
133+
// swiftlint: enable line_length
134+
return .replace
135+
}
136+
}

‎Coder Desktop/Coder Desktop/VPNService.swift

+73-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import NetworkExtension
2+
import os
13
import SwiftUI
24

35
@MainActor
@@ -7,6 +9,7 @@ protocol VPNService: ObservableObject {
79
func start() async
810
// Stop must be idempotent
911
func stop() async
12+
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?)
1013
}
1114

1215
enum VPNServiceState: Equatable {
@@ -18,13 +21,80 @@ enum VPNServiceState: Equatable {
1821
}
1922

2023
enum VPNServiceError: Error, Equatable {
21-
// TODO:
22-
case exampleError
24+
case internalError(String)
25+
case systemExtensionError(SystemExtensionState)
26+
case networkExtensionError(NetworkExtensionState)
27+
case longTestError
2328

2429
var description: String {
2530
switch self {
26-
case .exampleError:
31+
case .longTestError:
2732
return "This is a long error to test the UI with long errors"
33+
case let .internalError(description):
34+
return "Internal Error: \(description)"
35+
case let .systemExtensionError(state):
36+
return state.description
37+
case let .networkExtensionError(state):
38+
return state.description
39+
}
40+
}
41+
}
42+
43+
@MainActor
44+
final class CoderVPNService: NSObject, VPNService {
45+
var logger = Logger(subsystem: "com.coder.Coder-Desktop", category: "vpn")
46+
@Published var tunnelState: VPNServiceState = .disabled
47+
@Published var sysExtnState: SystemExtensionState = .uninstalled
48+
@Published var neState: NetworkExtensionState = .unconfigured
49+
var state: VPNServiceState {
50+
guard sysExtnState == .installed else {
51+
return .failed(.systemExtensionError(sysExtnState))
52+
}
53+
guard neState == .enabled || neState == .disbled else {
54+
return .failed(.networkExtensionError(neState))
55+
}
56+
return tunnelState
57+
}
58+
59+
@Published var agents: [Agent] = []
60+
61+
override init() {
62+
super.init()
63+
installSystemExtension()
64+
}
65+
66+
func start() async {
67+
tunnelState = .connecting
68+
await enableNetworkExtension()
69+
70+
// TODO: enable communication with the NetworkExtension to track state and agents. For
71+
// now, just pretend it worked...
72+
tunnelState = .connected
73+
}
74+
75+
func stop() async {
76+
tunnelState = .disconnecting
77+
await disableNetworkExtension()
78+
// TODO: determine when the NetworkExtension is completely disconnected
79+
tunnelState = .disabled
80+
}
81+
82+
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?) {
83+
Task {
84+
if proto != nil {
85+
await configureNetworkExtension(proto: proto!)
86+
// this just configures the VPN, it doesn't enable it
87+
tunnelState = .disabled
88+
} else {
89+
do {
90+
try await removeNetworkExtension()
91+
neState = .unconfigured
92+
tunnelState = .disabled
93+
} catch {
94+
logger.error("failed to remoing network extension: \(error)")
95+
neState = .failed(error.localizedDescription)
96+
}
97+
}
2898
}
2999
}
30100
}

‎Coder Desktop/Coder DesktopTests/VPNMenuTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct VPNMenuTests {
110110
#expect(try !toggle.isOn())
111111

112112
vpn.onStart = {
113-
vpn.state = .failed(.exampleError)
113+
vpn.state = .failed(.longTestError)
114114
}
115115
await vpn.start()
116116

‎Coder Desktop/Coder DesktopTests/VPNStateTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ struct VPNStateTests {
5555

5656
@Test
5757
func testFailedState() async throws {
58-
vpn.state = .failed(.exampleError)
58+
vpn.state = .failed(.longTestError)
5959

6060
try await ViewHosting.host(view.environmentObject(vpn)) {
6161
try await sut.inspection.inspect { view in
6262
let text = try view.find(ViewType.Text.self)
63-
#expect(try text.string() == VPNServiceError.exampleError.description)
63+
#expect(try text.string() == VPNServiceError.longTestError.description)
6464
}
6565
}
6666
}

‎Coder Desktop/VPN/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<key>NetworkExtension</key>
88
<dict>
99
<key>NEMachServiceName</key>
10-
<string>$(TeamIdentifierPrefix)com.example.app-group.MySystemExtension</string>
10+
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop.VPN</string>
1111
<key>NEProviderClasses</key>
1212
<dict>
1313
<key>com.apple.networkextension.packet-tunnel</key>

‎Coder Desktop/VPN/PacketTunnelProvider.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import os
55
let CTLIOCGINFO: UInt = 0xC064_4E03
66

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

1111
public var tunnelFileDescriptor: Int32? {
@@ -41,6 +41,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
4141
}
4242

4343
override func startTunnel(options _: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
44+
logger.debug("startTunnel called")
4445
guard manager == nil else {
4546
logger.error("startTunnel called with non-nil Manager")
4647
completionHandler(nil)
@@ -57,6 +58,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
5758
}
5859

5960
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
61+
logger.debug("stopTunnel called")
6062
guard manager == nil else {
6163
logger.error("stopTunnel called with nil Manager")
6264
completionHandler()
@@ -75,10 +77,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
7577

7678
override func sleep(completionHandler: @escaping () -> Void) {
7779
// Add code here to get ready to sleep.
80+
logger.debug("sleep called")
7881
completionHandler()
7982
}
8083

8184
override func wake() {
8285
// Add code here to wake up.
86+
logger.debug("wake called")
8387
}
8488
}

‎Coder Desktop/VPN/VPN.entitlements

+5-8
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>com.apple.developer.networking.networkextension</key>
6+
<array>
7+
<string>packet-tunnel-provider</string>
8+
</array>
59
<key>com.apple.security.app-sandbox</key>
610
<true/>
711
<key>com.apple.security.application-groups</key>
812
<array>
9-
<string>$(TeamIdentifierPrefix)com.example.app-group</string>
10-
</array>
11-
<key>com.apple.developer.networking.networkextension</key>
12-
<array>
13-
<string>packet-tunnel-provider</string>
14-
<string>app-proxy-provider</string>
15-
<string>content-filter-provider</string>
16-
<string>dns-proxy</string>
13+
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop</string>
1714
</array>
1815
</dict>
1916
</plist>

0 commit comments

Comments
 (0)
Please sign in to comment.