forked from pia-foss/mac-split-tunnel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ProxyAppDefault.swift
185 lines (156 loc) · 6.84 KB
/
ProxyAppDefault.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import Foundation
import NetworkExtension
import SystemExtensions
import os.log
/**
The ProxyAppDefault class implements the ProxyApp protocol.
The process using this class will run in the user space, acting as the "frontend"
for the root network extension process.
This class is used by the GUI managing the system extension.
The GUI was used mainly during the development phase.
PIA desktop client manages the extension through ProxyCLI
and since the integration has been completed this client have not been developed further
*/
class ProxyAppDefault : ProxyApp {
var proxyManager: NETransparentProxyManager?
var extensionRequestDelegate = ExtensionRequestDelegate()
var bypassApps: [String] = []
var vpnOnlyApps: [String] = []
var networkInterface: String = ""
static let proxyManagerName = "PIA Split Tunnel Proxy"
static let serverAddress = "127.0.0.1"
func setBypassApps(apps: [String]) -> Void {
self.bypassApps = apps
}
func setVpnOnlyApps(apps: [String]) -> Void {
self.vpnOnlyApps = apps
}
func setNetworkInterface(interface: String) -> Void {
self.networkInterface = interface
}
func activateExtension() -> Bool {
os_log("activating extension!")
guard let extensionIdentifier = getExtensionBundleID().bundleIdentifier else {
os_log("cannot find the extension bundle ID!")
return false
}
os_log("found the extension: %s", extensionIdentifier)
let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main)
activationRequest.delegate = extensionRequestDelegate
OSSystemExtensionManager.shared.submitRequest(activationRequest)
os_log("extension activation request sent!")
return true
}
func deactivateExtension() -> Bool {
os_log("deactivating extension!")
guard let extensionIdentifier = getExtensionBundleID().bundleIdentifier else {
os_log("cannot find the extension to deactivate!")
return false
}
os_log("found the extension: %s", extensionIdentifier)
let deactivationRequest = OSSystemExtensionRequest.deactivationRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main)
deactivationRequest.delegate = extensionRequestDelegate
OSSystemExtensionManager.shared.submitRequest(deactivationRequest)
os_log("extension deactivation request sent!")
return true
}
func loadOrInstallProxyManager() -> Bool {
tryLoadProxyManager() {
if self.proxyManager == nil {
self.createManager()
}
}
return true
}
private func tryLoadProxyManager(completion: @escaping () -> Void) {
os_log("trying to load an existing proxy manager!")
NETransparentProxyManager.loadAllFromPreferences() { loadedManagers, error in
if error != nil {
os_log("error while loading manager!")
} else if loadedManagers!.isEmpty {
os_log("no managers found")
} else if loadedManagers!.count == 1 {
self.proxyManager = loadedManagers!.first
os_log("manager loaded!")
} else {
os_log("ERROR: found more than 1 manager!")
}
completion()
}
}
private func createManager() {
os_log("creating manager!")
self.proxyManager = NETransparentProxyManager()
self.proxyManager!.localizedDescription = ProxyAppDefault.proxyManagerName
let tunnelProtocol = NETunnelProviderProtocol()
// This must match the app extension bundle identifier
tunnelProtocol.providerBundleIdentifier = getExtensionBundleID().bundleIdentifier
// As for NETransparentProxyNetworkSettings unsure about the meaning of
// this setting in the context of a NETransparentProxy.
// Docs say it should be the VPN address
//
// Setting it to localhost for now, until a 'proper' solution is found
tunnelProtocol.serverAddress = ProxyAppDefault.serverAddress
self.proxyManager!.protocolConfiguration = tunnelProtocol
self.proxyManager!.isEnabled = true
os_log("manager created!")
}
func startProxy() -> Bool {
os_log("starting proxy extension!")
if self.proxyManager == nil {
os_log("no manager is found!")
return false
}
self.proxyManager!.saveToPreferences { errorSave in
self.proxyManager!.loadFromPreferences { errorLoad in
// might want to refactor this code since the return value of startProxy()
// cannot show if an error occurred in these closures
if errorSave != nil || errorLoad != nil {
os_log("error while loading preferences!")
return
}
// The NETunnelProviderSession API is used to control network tunnel
// services provided by NETunnelProvider implementations.
if let session = self.proxyManager!.connection as? NETunnelProviderSession {
do {
// This function is used to start the tunnel (the proxy)
// passing it the following settings
try session.startTunnel(options: [
"bypassApps" : self.bypassApps,
"vpnOnlyApps" : self.vpnOnlyApps,
"bindInterface" : self.networkInterface,
"serverAddress" : ProxyAppDefault.serverAddress,
"logFile" : "/tmp/STProxy.log",
"logLevel" : "debug",
"routeVpn" : true,
"isConnected" : true,
// The name of the unix group pia whitelists in the firewall
// This may be different when PIA is white-labeled
"whitelistGroupName" : "piavpn"
] as [String : Any])
} catch {
os_log("startProxy error!")
print(error)
}
} else {
os_log("error getting the proxy manager connection")
}
}
}
return true
}
func stopProxy() -> Bool {
os_log("stopping proxy extension!")
if self.proxyManager == nil {
os_log("no manager is found!")
return false
}
if let session = self.proxyManager!.connection as? NETunnelProviderSession {
session.stopTunnel()
} else {
os_log("error getting the proxy manager connection")
return false
}
return true
}
}