-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathVPNService.swift
128 lines (115 loc) · 3.99 KB
/
VPNService.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
import NetworkExtension
import os
import SwiftUI
@MainActor
protocol VPNService: ObservableObject {
var state: VPNServiceState { get }
var agents: [Agent] { get }
func start() async
// Stop must be idempotent
func stop() async
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?)
}
enum VPNServiceState: Equatable {
case disabled
case connecting
case disconnecting
case connected
case failed(VPNServiceError)
}
enum VPNServiceError: Error, Equatable {
case internalError(String)
case systemExtensionError(SystemExtensionState)
case networkExtensionError(NetworkExtensionState)
case longTestError
var description: String {
switch self {
case .longTestError:
return "This is a long error to test the UI with long errors"
case let .internalError(description):
return "Internal Error: \(description)"
case let .systemExtensionError(state):
return state.description
case let .networkExtensionError(state):
return state.description
}
}
}
@MainActor
final class CoderVPNService: NSObject, VPNService {
var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "vpn")
@Published var tunnelState: VPNServiceState = .disabled
@Published var sysExtnState: SystemExtensionState = .uninstalled
@Published var neState: NetworkExtensionState = .unconfigured
var state: VPNServiceState {
guard sysExtnState == .installed else {
return .failed(.systemExtensionError(sysExtnState))
}
guard neState == .enabled || neState == .disabled else {
return .failed(.networkExtensionError(neState))
}
return tunnelState
}
@Published var agents: [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
// only stores a weak reference to the delegate.
var systemExtnDelegate: SystemExtensionDelegate<CoderVPNService>?
override init() {
super.init()
installSystemExtension()
Task {
await loadNetworkExtension()
}
}
var startTask: Task<Void, Never>?
func start() async {
if await startTask?.value != nil {
return
}
startTask = Task {
tunnelState = .connecting
await enableNetworkExtension()
// TODO: enable communication with the NetworkExtension to track state and agents. For
// now, just pretend it worked...
tunnelState = .connected
}
defer { startTask = nil }
await startTask?.value
}
var stopTask: Task<Void, Never>?
func stop() async {
// Wait for a start operation to finish first
await startTask?.value
guard state == .connected else { return }
if await stopTask?.value != nil {
return
}
stopTask = Task {
tunnelState = .disconnecting
await disableNetworkExtension()
// TODO: determine when the NetworkExtension is completely disconnected
tunnelState = .disabled
}
defer { stopTask = nil }
await stopTask?.value
}
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?) {
Task {
if proto != nil {
await configureNetworkExtension(proto: proto!)
// this just configures the VPN, it doesn't enable it
tunnelState = .disabled
} else {
do {
try await removeNetworkExtension()
neState = .unconfigured
tunnelState = .disabled
} catch {
logger.error("failed to remoing network extension: \(error)")
neState = .failed(error.localizedDescription)
}
}
}
}
}