Skip to content

Commit 038fe7d

Browse files
committed
Initial attempt at server switching support
1 parent c1e04e9 commit 038fe7d

5 files changed

Lines changed: 204 additions & 60 deletions

File tree

macos/networkextension/VPNSplitTunnelProvider.mm

Lines changed: 100 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#import <NetworkExtension/NetworkExtension.h>
66

77
#import "interfaceconfig.h"
8+
#import "utils.h"
89
#import "wireguardtunnel.h"
910

1011
#include <atomic>
@@ -55,13 +56,6 @@ - (id)init{
5556
return self;
5657
}
5758

58-
+ (NSError*) makeError:(NSInteger)code
59-
withDescription:(NSString*)desc {
60-
return [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier]
61-
code:code
62-
userInfo:@{NSLocalizedDescriptionKey: desc}];
63-
}
64-
6559
+ (nw_endpoint_t)convertEndpoint:(NWEndpoint*)old {
6660
if (old == nil) {
6761
return nil;
@@ -149,13 +143,11 @@ - (void)startProxyWithOptions:(NSDictionary<NSString *,id> *)options
149143
m_handledUnknown = 0;
150144

151145
// Parse the configuration
152-
InterfaceConfig* config = [[InterfaceConfig alloc] initFromDict:options];
153-
if (!config) {
154-
completionHandler([VPNSplitTunnelProvider makeError:1
155-
withDescription:@"invalid configuration"]);
146+
_config = [[InterfaceConfig alloc] initFromDict:options];
147+
if (!self.config) {
148+
completionHandler(vpnProviderError(NEProviderStopReasonConfigurationFailed));
156149
return;
157150
}
158-
_config = config;
159151

160152
self.wireguard = [WireguardTunnel new];
161153
self.settings = [[NETransparentProxyNetworkSettings alloc] initWithTunnelRemoteAddress:self.protocolConfiguration.serverAddress];
@@ -245,6 +237,12 @@ - (void)startProxyWithOptions:(NSDictionary<NSString *,id> *)options
245237
return;
246238
}
247239

240+
// Register a KVO observer to switch servers upon configuration change.
241+
[weakSelf addObserver:weakSelf
242+
forKeyPath:@"protocolConfiguration"
243+
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
244+
context:nil];
245+
248246
// Success
249247
completionHandler(nil);
250248
}];
@@ -260,10 +258,91 @@ - (void)stopProxyWithReason:(NEProviderStopReason)reason
260258
NSLog(@"handled udp flows: %lld", std::atomic_load(&m_handledUdpFlows));
261259
NSLog(@"handled unknown flows: %lld", std::atomic_load(&m_handledUnknown));
262260

261+
[self removeObserver:self
262+
forKeyPath:@"protocolConfiguration"];
263+
263264
[self.wireguard stopTunnelWithReason:reason
264265
completionHandler:completionHandler];
265266
}
266267

268+
- (void)observeValueForKeyPath:(NSString *)keyPath
269+
ofObject:(id)object
270+
change:(NSDictionary *)change
271+
context:(void *)context {
272+
// The only thing we should be observing is the protocolConfiguration
273+
if (![keyPath isEqual:@"protocolConfiguration"]) {
274+
return;
275+
}
276+
NSLog(@"configuration changed");
277+
278+
// Parse and update the configuration
279+
NETunnelProviderProtocol* proto = (NETunnelProviderProtocol*)self.protocolConfiguration;
280+
_config = [[InterfaceConfig alloc] initFromDict:proto.providerConfiguration];
281+
if (!self.config) {
282+
// We can't make sense of this configuration.
283+
[self.wireguard stopTunnelWithReason:NEProviderStopReasonConfigurationFailed
284+
completionHandler:^(){
285+
[self cancelProxyWithError:vpnProviderError(NEProviderStopReasonConfigurationFailed)];
286+
}];
287+
return;
288+
}
289+
290+
// Update the app exclusion settings.
291+
[self.vpnDisabledApps removeAllObjects];
292+
NSArray* apps = [proto.providerConfiguration objectForKey:@"apps"];
293+
if (apps) {
294+
NSEnumerator* iter = [apps objectEnumerator];
295+
while (id appId = [iter nextObject]) {
296+
if (![appId isKindOfClass:[NSString class]]) {
297+
continue;
298+
}
299+
#ifdef MZ_DEBUG
300+
NSLog(@"excluding app %@ from VPN", appId);
301+
#endif
302+
[self.vpnDisabledApps addObject: appId];
303+
}
304+
}
305+
// TODO: We probably need to update/reset connections if they changed their exclusion status.
306+
// but how?
307+
308+
// YOLO: Does this do anything....
309+
[self performSelector:@selector(fetchFlowStatesWithCompletionHandler:)
310+
withObject:^(NSArray* result){
311+
NSLog(@"flow count: %lu", result.count);
312+
for (NSObject* obj in result) {
313+
NSLog(@"flow state: %@", NSStringFromClass(obj.class));
314+
}
315+
}];
316+
317+
// Check if the server identitiy changed. Do nothing if no changes.
318+
NETunnelProviderProtocol* old = [change objectForKey:@"old"];
319+
if (old && [old isKindOfClass:NETunnelProviderProtocol.class]) {
320+
NSString* oldPubKey = [old.providerConfiguration objectForKey:@"serverPublicKey"];
321+
if (oldPubKey && [oldPubKey isKindOfClass:NSString.class]) {
322+
if ([oldPubKey isEqual:self.config.serverPublicKey]) {
323+
return;
324+
}
325+
}
326+
}
327+
328+
// Shutdown the old wireguard peer and start a new one.
329+
self.reasserting = TRUE;
330+
[self.wireguard.peer stopWithReason:NEProviderStopReasonSuperceded
331+
completionHandler:^(){
332+
// Create and start a new peer.
333+
self.wireguard.peer = [[WireguardPeer alloc] initWithOptions:self.config
334+
andTunnel:self.wireguard];
335+
[self.wireguard.peer startWithOptions:self.config
336+
completionHandler:^(NSError*err){
337+
if (err) {
338+
[self cancelProxyWithError:err];
339+
} else {
340+
self.reasserting = FALSE;
341+
}
342+
}];
343+
}];
344+
}
345+
267346
- (BOOL)matchAppFlow:(NEAppProxyFlow*)flow {
268347
// Without metadata - always direct the flow into the VPN.
269348
if (flow.metaData == nil) {
@@ -337,14 +416,14 @@ - (void)handleAppMessage:(NSData *)messageData
337416
error:&error];
338417
if (error != nil) {
339418
NSLog(@"app message error: %@", error.localizedDescription);
340-
[VPNSplitTunnelProvider sendAppError:error completionHandler:completionHandler];
419+
[VPNSplitTunnelProvider sendAppResponse:error completionHandler:completionHandler];
341420
return;
342421
}
343422
NSString* action = [msg decodeObjectOfClass:NSString.class forKey:@"action"];
344423
if (!action) {
345424
NSLog(@"app message invalid action");
346-
NSError* error = [VPNSplitTunnelProvider makeError:1 withDescription:@"invalid app message invalid"];
347-
[VPNSplitTunnelProvider sendAppError:error completionHandler:completionHandler];
425+
NSError* error = vpnProviderError(NEProviderStopReasonConfigurationFailed);
426+
[VPNSplitTunnelProvider sendAppResponse:error completionHandler:completionHandler];
348427
return;
349428
}
350429

@@ -366,8 +445,8 @@ - (void)handleAppMessage:(NSData *)messageData
366445

367446
// Wireguard Tunnel messages
368447
if ([action isEqualToString:@"status"]) {
369-
[VPNSplitTunnelProvider sendAppObject:self.wireguard.status
370-
completionHandler:completionHandler];
448+
[VPNSplitTunnelProvider sendAppResponse:self.wireguard.status
449+
completionHandler:completionHandler];
371450
return;
372451
}
373452

@@ -385,33 +464,20 @@ - (void)handleAppMessage:(NSData *)messageData
385464
[VPNSplitTunnelProvider sendAppResponse:nil completionHandler:completionHandler];
386465
}
387466

388-
+ (void)sendAppResponse:(NSData*) responseData
467+
+ (void)sendAppResponse:(id) obj
389468
completionHandler:(void (^)(NSData*)) completionHandler {
390469
if (!completionHandler) {
391470
return;
392471
}
393-
completionHandler(responseData);
394-
}
395472

396-
+ (void)sendAppObject:(id) obj
397-
completionHandler:(void (^)(NSData*)) completionHandler {
398473
NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
399-
if ([obj respondsToSelector:@selector(encodeWithCoder:)]) {
474+
if ([obj isKindOfClass:[NSError class]]) {
475+
[encoder encodeObject:obj forKey:@"error"];
476+
} else if ([obj respondsToSelector:@selector(encodeWithCoder:)]) {
400477
[obj encodeWithCoder: encoder];
401478
}
402479
[encoder finishEncoding];
403480
completionHandler(encoder.encodedData);
404481
}
405482

406-
+ (void)sendAppError:(NSError*) error
407-
completionHandler:(void (^)(NSData*)) completionHandler {
408-
if (!completionHandler) {
409-
return;
410-
}
411-
NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
412-
[encoder encodeObject:error forKey:@"error"];
413-
[encoder finishEncoding];
414-
completionHandler(encoder.encodedData);
415-
}
416-
417483
@end

macos/networkextension/utils.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,9 @@
88

99
#ifndef UTILS_H
1010

11-
typedef enum {
12-
kVPNSuccess = 0,
13-
kVPNErrInvalidConfig = 1,
14-
kVPNErrTunnelNotRunning = 2,
15-
} VPNErrorType;
16-
1711
nw_endpoint_t convertEndpoint(NWEndpoint* ep);
1812
NSUInteger getWorkerCount();
19-
NSError* vpnProviderError(VPNErrorType err, NSString* msg);
13+
NSError* vpnProviderError(NEProviderStopReason reason);
2014
NSError* vpnPosixError(int code, NSString* msg);
2115

2216
#endif // UTILS_H

macos/networkextension/utils.mm

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,81 @@ nw_endpoint_t convertEndpoint(NWEndpoint* old) {
6262
return nw_endpoint_create_host(host.hostname.UTF8String, host.port.UTF8String);
6363
}
6464

65-
NSError* vpnProviderError(VPNErrorType err, NSString* description) {
65+
NSError* vpnProviderError(NEProviderStopReason reason) {
66+
NSDictionary<NSString *,id>* info = nil;
67+
switch (reason) {
68+
case NEProviderStopReasonNone:
69+
info = @{NSLocalizedDescriptionKey: @"No specific reason"};
70+
break;
71+
72+
case NEProviderStopReasonUserInitiated:
73+
info = @{NSLocalizedDescriptionKey: @"The user stopped the provider extension"};
74+
break;
75+
76+
case NEProviderStopReasonProviderFailed:
77+
info = @{NSLocalizedDescriptionKey: @"The provider failed to function correctly"};
78+
break;
79+
80+
case NEProviderStopReasonNoNetworkAvailable:
81+
info = @{NSLocalizedDescriptionKey: @"No network connectivity is currently available"};
82+
break;
83+
84+
case NEProviderStopReasonUnrecoverableNetworkChange:
85+
info = @{NSLocalizedDescriptionKey: @"The device's network connectivity changed"};
86+
break;
87+
88+
case NEProviderStopReasonProviderDisabled:
89+
info = @{NSLocalizedDescriptionKey: @"The provider was disabled"};
90+
break;
91+
92+
case NEProviderStopReasonAuthenticationCanceled:
93+
info = @{NSLocalizedDescriptionKey: @"The authentication process was canceled"};
94+
break;
95+
96+
case NEProviderStopReasonConfigurationFailed:
97+
info = @{NSLocalizedDescriptionKey: @"The configuration is invalid"};
98+
break;
99+
100+
case NEProviderStopReasonIdleTimeout:
101+
info = @{NSLocalizedDescriptionKey: @"The session timed out"};
102+
break;
103+
104+
case NEProviderStopReasonConfigurationDisabled:
105+
info = @{NSLocalizedDescriptionKey: @"The configuration was disabled"};
106+
break;
107+
108+
case NEProviderStopReasonConfigurationRemoved:
109+
info = @{NSLocalizedDescriptionKey: @"The configuration was removed"};
110+
break;
111+
112+
case NEProviderStopReasonSuperceded:
113+
info = @{NSLocalizedDescriptionKey: @"The configuration was superceded by a higher-priority configuration"};
114+
break;
115+
116+
case NEProviderStopReasonUserLogout:
117+
info = @{NSLocalizedDescriptionKey: @"The user logged out"};
118+
break;
119+
120+
case NEProviderStopReasonConnectionFailed:
121+
info = @{NSLocalizedDescriptionKey: @"The connection failed"};
122+
break;
123+
124+
case NEProviderStopReasonSleep:
125+
info = @{NSLocalizedDescriptionKey: @"A stop reason indicating the configuration enabled disconnect on sleep and the device went to sleep"};
126+
break;
127+
128+
case NEProviderStopReasonInternalError:
129+
info = @{NSLocalizedDescriptionKey: @"The provider encountered an internal error"};
130+
break;
131+
132+
case NEProviderStopReasonAppUpdate:
133+
default:
134+
break;
135+
}
136+
66137
return [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier]
67-
code:(NSUInteger)err
68-
userInfo:@{NSLocalizedDescriptionKey: description}];
138+
code:(NSUInteger)reason
139+
userInfo:info];
69140
}
70141

71142
NSError* vpnPosixError(int code, NSString* desc) {

macos/networkextension/wireguardpeer.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ - (void) socketWorker:(id)arg {
170170

171171
- (void) stopWithReason:(NEProviderStopReason)reason
172172
completionHandler:(void (^)()) completionHandler {
173-
[self cancelWithError:vpnPosixError(ECANCELED, @"wireguard peer stopped")];
173+
[self cancelWithError:vpnProviderError(reason)];
174174
completionHandler();
175175
}
176176

src/platforms/macos/macosextensioncontroller.mm

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,32 @@ - (void)notifyStatusChanged:(NSNotification*)notify;
190190
}
191191
[options setObject:vpnDisabledApps forKey:@"apps"];
192192

193-
// Start the split tunnel proxy.
194-
NSError* error = nil;
195-
NETunnelProviderSession* session =
196-
static_cast<NETunnelProviderSession*>(m_manager.connection);
197-
BOOL okay = [session startTunnelWithOptions:options andReturnError:&error];
198-
if (error) {
199-
logger.warning() << "proxy start error:" << error.localizedDescription;
200-
} else if (!okay) {
201-
logger.warning() << "proxy start failed";
202-
} else {
203-
// Save the session and retain it.
204-
m_session = [session retain];
205-
}
193+
// Update the tunnel configuration.
194+
NETunnelProviderProtocol* proto =
195+
static_cast<NETunnelProviderProtocol*>(m_manager.protocolConfiguration);
196+
proto.providerConfiguration = options;
197+
[m_manager saveToPreferencesWithCompletionHandler:^(NSError* error) {
198+
if (error) {
199+
logger.warning() << "prefs update error:" << error.localizedDescription;
200+
return;
201+
}
202+
203+
// If we don't already have a session - start one.
204+
if (m_session) {
205+
return;
206+
}
207+
NETunnelProviderSession* session =
208+
static_cast<NETunnelProviderSession*>(m_manager.connection);
209+
BOOL okay = [session startTunnelWithOptions:options andReturnError:&error];
210+
if (error) {
211+
logger.warning() << "proxy start error:" << error.localizedDescription;
212+
} else if (!okay) {
213+
logger.warning() << "proxy start failed";
214+
} else {
215+
// Save the session and retain it.
216+
m_session = [session retain];
217+
}
218+
}];
206219
}
207220

208221
void MacOSExtensionController::deactivate() {
@@ -304,8 +317,8 @@ - (void)notifyEnabledChanged:(NSNotification*)notify {
304317
}
305318

306319
- (void)notifyStatusChanged:(NSNotification*)notify {
307-
NEVPNStatus status = static_cast<NEVPNConnection*>(notify.object).status;
308-
QMetaObject::invokeMethod(self.parent, "extStatusChange", Q_ARG(int, status));
320+
NEVPNConnection* conn = static_cast<NEVPNConnection*>(notify.object);
321+
QMetaObject::invokeMethod(self.parent, "extStatusChange", Q_ARG(int, conn.status));
309322
}
310323

311324
@end

0 commit comments

Comments
 (0)