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