mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-03-17 00:00:03 +08:00
225 lines
7.6 KiB
Plaintext
225 lines
7.6 KiB
Plaintext
//
|
|
// OpenVPNAdapter.m
|
|
// OpenVPN iOS Client
|
|
//
|
|
// Created by Sergey Abramchuk on 11.02.17.
|
|
//
|
|
//
|
|
|
|
#import <sys/socket.h>
|
|
#import <sys/un.h>
|
|
#import <sys/stat.h>
|
|
#import <sys/ioctl.h>
|
|
#import <arpa/inet.h>
|
|
|
|
#import <NetworkExtension/NetworkExtension.h>
|
|
|
|
#import "OpenVPNError.h"
|
|
#import "OpenVPNEvent.h"
|
|
#import "OpenVPNClient.h"
|
|
|
|
#import "OpenVPNAdapter.h"
|
|
#import "OpenVPNAdapter+Client.h"
|
|
#import "OpenVPNAdapter+Provider.h"
|
|
|
|
NSString *const OpenVPNClientErrorDomain = @"OpenVPNClientErrorDomain";
|
|
|
|
NSString *const OpenVPNClientErrorFatalKey = @"OpenVPNClientErrorFatalKey";
|
|
NSString *const OpenVPNClientErrorEventKey = @"OpenVPNClientErrorEventKey";
|
|
|
|
|
|
@interface OpenVPNAdapter ()
|
|
|
|
@property OpenVPNClient *vpnClient;
|
|
|
|
@property CFSocketRef tunSocket;
|
|
@property CFSocketRef vpnSocket;
|
|
|
|
@property (weak, nonatomic) NEPacketTunnelFlow *packetFlow;
|
|
|
|
@end
|
|
|
|
|
|
@implementation OpenVPNAdapter (Client)
|
|
|
|
#pragma mark Sockets Configuration
|
|
|
|
static void socketCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
|
|
OpenVPNAdapter *adapter = (__bridge OpenVPNAdapter *)info;
|
|
|
|
switch (type) {
|
|
case kCFSocketDataCallBack:
|
|
// TODO: Handle received data and send it to the tun interface
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (BOOL)configureSockets {
|
|
int sockets[2];
|
|
if (socketpair(PF_LOCAL, SOCK_DGRAM, IPPROTO_IP, sockets) == -1) {
|
|
NSLog(@"Failed to create a pair of connected sockets: %@", [NSString stringWithUTF8String:strerror(errno)]);
|
|
return NO;
|
|
}
|
|
|
|
CFSocketContext socketCtxt = {0, (__bridge void *)self, NULL, NULL, NULL};
|
|
|
|
self.tunSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[0], kCFSocketDataCallBack, &socketCallback, &socketCtxt);
|
|
self.vpnSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[1], kCFSocketNoCallBack, NULL, NULL);
|
|
|
|
if (!self.tunSocket || !self.vpnSocket) {
|
|
NSLog(@"Failed to create core foundation sockets from native sockets");
|
|
return NO;
|
|
}
|
|
|
|
CFRunLoopSourceRef tunSocketSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, self.tunSocket, 0);
|
|
CFRunLoopAddSource(CFRunLoopGetMain(), tunSocketSource, kCFRunLoopCommonModes);
|
|
|
|
CFRelease(tunSocketSource);
|
|
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark Event and Log Handlers
|
|
|
|
- (void)handleEvent:(const ClientAPI::Event *)event {
|
|
NSAssert(self.delegate != nil, @"delegate property should not be nil");
|
|
|
|
NSString *eventName = [NSString stringWithUTF8String:event->name.c_str()];
|
|
OpenVPNEvent eventIdentifier = [self getEventIdentifierByName:eventName];
|
|
|
|
NSString *eventMessage = [NSString stringWithUTF8String:event->info.c_str()];
|
|
|
|
if (event->error) {
|
|
NSMutableDictionary *userInfo = [NSMutableDictionary new];
|
|
[userInfo setObject:@(event->fatal) forKey:OpenVPNClientErrorFatalKey];
|
|
[userInfo setObject:@(eventIdentifier) forKey:OpenVPNClientErrorEventKey];
|
|
|
|
if (eventMessage != nil && ![eventMessage isEqualToString:@""]) {
|
|
[userInfo setObject:eventMessage forKey:NSLocalizedDescriptionKey];
|
|
}
|
|
|
|
NSError *error = [NSError errorWithDomain:OpenVPNClientErrorDomain
|
|
code:OpenVPNErrorClientFailure
|
|
userInfo:[userInfo copy]];
|
|
|
|
[self.delegate handleError:error];
|
|
} else {
|
|
[self.delegate handleEvent:eventIdentifier message:eventMessage == nil || [eventMessage isEqualToString:@""] ? nil : eventMessage];
|
|
}
|
|
}
|
|
|
|
- (void)handleLog:(const ClientAPI::LogInfo *)log {
|
|
NSString *message = [NSString stringWithCString:log->text.c_str() encoding:NSUTF8StringEncoding];
|
|
NSLog(@"%@", message);
|
|
}
|
|
|
|
- (OpenVPNEvent)getEventIdentifierByName:(NSString *)eventName {
|
|
NSDictionary *events = @{
|
|
@"DISCONNECTED": @(OpenVPNEventDisconnected),
|
|
@"CONNECTED": @(OpenVPNEventConnected),
|
|
@"RECONNECTING": @(OpenVPNEventReconnecting),
|
|
@"RESOLVE": @(OpenVPNEventResolve),
|
|
@"WAIT": @(OpenVPNEventWait),
|
|
@"WAIT_PROXY": @(OpenVPNEventWaitProxy),
|
|
@"CONNECTING": @(OpenVPNEventConnecting),
|
|
@"GET_CONFIG": @(OpenVPNEventGetConfig),
|
|
@"ASSIGN_IP": @(OpenVPNEventAssignIP),
|
|
@"ADD_ROUTES": @(OpenVPNEventAddRoutes),
|
|
@"ECHO": @(OpenVPNEventEcho),
|
|
@"INFO": @(OpenVPNEventInfo),
|
|
@"PAUSE": @(OpenVPNEventPause),
|
|
@"RESUME": @(OpenVPNEventResume),
|
|
@"TRANSPORT_ERROR": @(OpenVPNEventTransportError),
|
|
@"TUN_ERROR": @(OpenVPNEventTunError),
|
|
@"CLIENT_RESTART": @(OpenVPNEventClientRestart),
|
|
@"AUTH_FAILED": @(OpenVPNEventAuthFailed),
|
|
@"CERT_VERIFY_FAIL": @(OpenVPNEventCertVerifyFail),
|
|
@"TLS_VERSION_MIN": @(OpenVPNEventTLSVersionMin),
|
|
@"CLIENT_HALT": @(OpenVPNEventClientHalt),
|
|
@"CONNECTION_TIMEOUT": @(OpenVPNEventConnectionTimeout),
|
|
@"INACTIVE_TIMEOUT": @(OpenVPNEventInactiveTimeout),
|
|
@"DYNAMIC_CHALLENGE": @(OpenVPNEventDynamicChallenge),
|
|
@"PROXY_NEED_CREDS": @(OpenVPNEventProxyNeedCreds),
|
|
@"PROXY_ERROR": @(OpenVPNEventProxyError),
|
|
@"TUN_SETUP_FAILED": @(OpenVPNEventTunSetupFailed),
|
|
@"TUN_IFACE_CREATE": @(OpenVPNEventTunIfaceCreate),
|
|
@"TUN_IFACE_DISABLED": @(OpenVPNEventTunIfaceDisabled),
|
|
@"EPKI_ERROR": @(OpenVPNEventEPKIError),
|
|
@"EPKI_INVALID_ALIAS": @(OpenVPNEventEPKIInvalidAlias),
|
|
};
|
|
|
|
OpenVPNEvent event = events[eventName] != nil ? (OpenVPNEvent)[(NSNumber *)events[eventName] unsignedIntegerValue] : OpenVPNEventUnknown;
|
|
return event;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation OpenVPNAdapter (Provider)
|
|
|
|
#pragma mark Client Configuration
|
|
|
|
- (BOOL)configureWithUsername:(NSString *)username password:(NSString *)password configuration:(NSData *)configuration error:(out NSError * __autoreleasing _Nullable *)error {
|
|
NSString *vpnConfiguration = [[NSString alloc] initWithData:configuration encoding:NSUTF8StringEncoding];
|
|
|
|
if (vpnConfiguration == nil) {
|
|
if (error) *error = [NSError errorWithDomain:OpenVPNClientErrorDomain code:OpenVPNErrorConfigurationFailure userInfo:@{
|
|
// TODO: Write error message
|
|
NSLocalizedDescriptionKey: @"Failed to ..."
|
|
}];
|
|
return NO;
|
|
}
|
|
|
|
ClientAPI::Config clientConfiguration;
|
|
clientConfiguration.content = std::string([vpnConfiguration UTF8String]);
|
|
clientConfiguration.connTimeout = 30;
|
|
|
|
self.vpnClient = new OpenVPNClient((__bridge void *)self);
|
|
|
|
ClientAPI::EvalConfig eval = self.vpnClient->eval_config(clientConfiguration);
|
|
if (eval.error) {
|
|
if (error) *error = [NSError errorWithDomain:OpenVPNClientErrorDomain code:OpenVPNErrorConfigurationFailure userInfo:@{
|
|
NSLocalizedDescriptionKey: [NSString stringWithUTF8String:eval.message.c_str()]
|
|
}];
|
|
return NO;
|
|
}
|
|
|
|
ClientAPI::ProvideCreds creds;
|
|
creds.username = [username UTF8String];
|
|
creds.password = [password UTF8String];
|
|
|
|
ClientAPI::Status creds_status = self.vpnClient->provide_creds(creds);
|
|
if (creds_status.error) {
|
|
if (error) *error = [NSError errorWithDomain:OpenVPNClientErrorDomain code:OpenVPNErrorConfigurationFailure userInfo:@{
|
|
NSLocalizedDescriptionKey: [NSString stringWithUTF8String:creds_status.message.c_str()]
|
|
}];
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark Connection Control
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
@implementation OpenVPNAdapter
|
|
|
|
- (void)dealloc {
|
|
delete self.vpnClient;
|
|
|
|
CFSocketInvalidate(self.tunSocket);
|
|
CFSocketInvalidate(self.vpnSocket);
|
|
|
|
CFRelease(self.tunSocket);
|
|
CFRelease(self.vpnSocket);
|
|
}
|
|
|
|
@end
|