diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bab57a3..98643fd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.0.9.1 +project(${PROJECT} VERSION 3.0.9.2 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 94205bd3..7e7837ac 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -219,7 +219,7 @@ if(APPLE) set(BUILD_IOS_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "iOS Application identifier") endif() if(NOT BUILD_IOS_GROUP_IDENTIFIER) - set(BUILD_IOS_GROUP_IDENTIFIER group.org.amnezia.AmneziaVPN.Guardian CACHE STRING "iOS Group identifier") + set(BUILD_IOS_GROUP_IDENTIFIER group.org.amnezia.AmneziaVPN CACHE STRING "iOS Group identifier") endif() if(NOT BUILD_VPN_DEVELOPMENT_TEAM) set(BUILD_VPN_DEVELOPMENT_TEAM X7UJ388FXK CACHE STRING "Amnezia VPN Development Team") diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7228e18d..491a68b8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -42,6 +42,7 @@ #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" +#include "platforms/ios/ios_controller.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -107,6 +108,7 @@ void AmneziaApplication::init() #if defined(Q_OS_IOS) setStartPageLogic(m_uiLogic->pageLogic()); + IosController::Instance()->initialize(); #endif m_engine->load(url); diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 68c55afb..5dc1b2e7 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -18,51 +18,35 @@ find_library(FW_STOREKIT StoreKit) find_library(FW_USERNOTIFICATIONS UserNotifications) set(LIBS ${LIBS} - ${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT} - ${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT} + ${FW_AUTHENTICATIONSERVICES} + ${FW_UIKIT} + ${FW_AVFOUNDATION} + ${FW_FOUNDATION} + ${FW_STOREKIT} ${FW_USERNOTIFICATIONS} ) set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ios_vpnprotocol.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h - #${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/bigint.h - #${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/bigintipv6addr.h - ${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/ipaddress.h - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ipaddressrange.h # TODO need refactor ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h ) +set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE) + set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ios_vpnprotocol.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm - ${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/ipaddress.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ipaddressrange.cpp # TODO need refactor ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm ) -#set(SOURCES ${SOURCES} -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c -# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift -#) - target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) @@ -112,23 +96,26 @@ target_compile_options(${PROJECT} PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\" -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" ) + +set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources) + target_sources(${PROJECT} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioslogger.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c - ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift + ${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardApp/LocalizationHelper.swift + ${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift + ${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c + ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift ) target_sources(${PROJECT} PRIVATE diff --git a/client/cmake/macos.cmake b/client/cmake/macos.cmake index 75ccf516..7b7cd381 100644 --- a/client/cmake/macos.cmake +++ b/client/cmake/macos.cmake @@ -1,5 +1,23 @@ message("MAC build") +find_library(FW_SYSTEMCONFIG SystemConfiguration) +find_library(FW_SERVICEMGMT ServiceManagement) +find_library(FW_SECURITY Security) +find_library(FW_COREWLAN CoreWLAN) +find_library(FW_NETWORK Network) +find_library(FW_USER_NOTIFICATIONS UserNotifications) +find_library(FW_NETWORK_EXTENSION NetworkExtension) + +set(LIBS ${LIBS} + ${FW_SYSTEMCONFIG} + ${FW_SERVICEMGMT} + ${FW_SECURITY} + ${FW_COREWLAN} + ${FW_NETWORK} + ${FW_USERNOTIFICATIONS} + ${FW_NETWORK_EXTENSION} +) + set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE) set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) @@ -18,21 +36,10 @@ set(MACOSX_BUNDLE_ICON_FILE app.icns) set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) set(SOURCES ${SOURCES} ${ICON_FILE}) - - -find_library(FW_SYSTEMCONFIG SystemConfiguration) -find_library(FW_SERVICEMGMT ServiceManagement) -find_library(FW_SECURITY Security) -find_library(FW_COREWLAN CoreWLAN) -find_library(FW_NETWORK Network) -find_library(FW_USER_NOTIFICATIONS UserNotifications) - -target_link_libraries(${PROJECT} PRIVATE ${FW_SYSTEMCONFIG}) -target_link_libraries(${PROJECT} PRIVATE ${FW_SERVICEMGMT}) -target_link_libraries(${PROJECT} PRIVATE ${FW_SECURITY}) -target_link_libraries(${PROJECT} PRIVATE ${FW_COREWLAN}) -target_link_libraries(${PROJECT} PRIVATE ${FW_NETWORK}) -target_link_libraries(${PROJECT} PRIVATE ${FW_USER_NOTIFICATIONS}) +target_compile_options(${PROJECT} PRIVATE + -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\" + -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" +) # Get SDK path execute_process( diff --git a/client/logger.cpp b/client/logger.cpp index e09792b1..46ce53f0 100644 --- a/client/logger.cpp +++ b/client/logger.cpp @@ -76,7 +76,7 @@ bool Logger::init() m_file.setTextModeEnabled(true); m_textStream.setDevice(&m_file); -#ifndef QT_DEBUG +#if !defined(QT_DEBUG) || defined(Q_OS_IOS) qInstallMessageHandler(debugMessageHandler); #endif diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h new file mode 100644 index 00000000..0750f7cd --- /dev/null +++ b/client/platforms/ios/ios_controller.h @@ -0,0 +1,84 @@ +#ifndef IOS_CONTROLLER_H +#define IOS_CONTROLLER_H + +#include "protocols/vpnprotocol.h" + +#ifdef __OBJC__ +#import +@class NETunnelProviderManager; +#endif + +using namespace amnezia; + +struct Action { + static const char* start; + static const char* restart; + static const char* stop; + static const char* getTunnelId; + static const char* getStatus; +}; + +struct MessageKey { + static const char* action; + static const char* tunnelId; + static const char* config; + static const char* errorCode; + static const char* host; + static const char* port; + static const char* isOnDemand; +}; + +class IosController : public QObject +{ + Q_OBJECT + +public: + static IosController* Instance(); + + virtual ~IosController() override = default; + + bool initialize(); + bool connectVpn(amnezia::Proto proto, const QJsonObject& configuration); + void disconnectVpn(); + + void vpnStatusDidChange(void *pNotification); + void vpnConfigurationDidChange(void *pNotification); + + void getBackendLogs(std::function &&callback); + void checkStatus(); +signals: + void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void bytesChanged(quint64 receivedBytes, quint64 sentBytes); + +protected slots: + + +private: + explicit IosController(); + + bool setupOpenVPN(); + bool setupCloak(); + bool setupWireGuard(); + + bool startOpenVPN(const QString &config); + bool startWireGuard(const QString &jsonConfig); + + void startTunnel(); + +private: + void *m_iosControllerWrapper{}; +#ifdef __OBJC__ + NETunnelProviderManager *m_currentTunnel{}; + NSString *m_serverAddress{}; + bool isOurManager(NETunnelProviderManager* manager); + void sendVpnExtensionMessage(NSDictionary* message, std::function callback); +#endif + + amnezia::Proto m_proto; + QJsonObject m_rawConfig; + QString m_tunnelId; + uint64_t m_txBytes; + uint64_t m_rxBytes; +}; + +#endif // IOS_CONTROLLER_H diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm new file mode 100644 index 00000000..c8e27252 --- /dev/null +++ b/client/platforms/ios/ios_controller.mm @@ -0,0 +1,475 @@ +#include "ios_controller.h" + +#include +#include +#include +#include +#include +#include + +#include "../protocols/vpnprotocol.h" +#import "ios_controller_wrapper.h" + +#import +#import +#import +#import + + +const char* Action::start = "start"; +const char* Action::restart = "restart"; +const char* Action::stop = "stop"; +const char* Action::getTunnelId = "getTunnelId"; +const char* Action::getStatus = "status"; + +const char* MessageKey::action = "action"; +const char* MessageKey::tunnelId = "tunnelId"; +const char* MessageKey::config = "config"; +const char* MessageKey::errorCode = "errorCode"; +const char* MessageKey::host = "host"; +const char* MessageKey::port = "port"; +const char* MessageKey::isOnDemand = "is-on-demand"; + +VpnProtocol::VpnConnectionState iosStatusToState(NEVPNStatus status) { + switch (status) { + case NEVPNStatusInvalid: + return VpnProtocol::VpnConnectionState::Unknown; + case NEVPNStatusDisconnected: + return VpnProtocol::VpnConnectionState::Disconnected; + case NEVPNStatusConnecting: + return VpnProtocol::VpnConnectionState::Connecting; + case NEVPNStatusConnected: + return VpnProtocol::VpnConnectionState::Connected; + case NEVPNStatusReasserting: + return VpnProtocol::VpnConnectionState::Connecting; + case NEVPNStatusDisconnecting: + return VpnProtocol::VpnConnectionState::Disconnecting; + default: + return VpnProtocol::VpnConnectionState::Unknown; +} +} + +namespace { +IosController* s_instance = nullptr; +} + +IosController::IosController() : QObject() +{ + qDebug() << "IosController::IosController() init"; + s_instance = this; + m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this]; + + [[NSNotificationCenter defaultCenter] + removeObserver: (__bridge NSObject *)m_iosControllerWrapper]; + [[NSNotificationCenter defaultCenter] + addObserver: (__bridge NSObject *)m_iosControllerWrapper selector:@selector(vpnStatusDidChange:) name:NEVPNStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver: (__bridge NSObject *)m_iosControllerWrapper selector:@selector(vpnConfigurationDidChange:) name:NEVPNConfigurationChangeNotification object:nil]; + +} + +IosController* IosController::Instance() { + if (!s_instance) { + s_instance = new IosController(); + } + + return s_instance; +} + +bool IosController::initialize() +{ + qDebug() << "IosController::initialize"; + + __block bool ok = true; + [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray * _Nullable managers, NSError * _Nullable error) { + @try { + if (error) { + qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String]; + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + ok = false; + return; + } + + NSInteger managerCount = managers.count; + qDebug() << "IosController::initialize : We have received managers:" << (long)managerCount; + + + for (NETunnelProviderManager *manager in managers) { + if (manager.connection.status == NEVPNStatusConnected) { + m_currentTunnel = manager; + qDebug() << "IosController::initialize : VPN already connected"; + break; + + // TODO: show connected state + } + } + } + @catch (NSException *exception) { + qDebug() << "IosController::setTunnel : exception" << QString::fromNSString(exception.reason); + ok = false; + } + }]; + + return ok; +} + +bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configuration) +{ + qDebug() << "IosController::connectVpn called from thread:" << QThread::currentThread(); + + m_proto = proto; + m_rawConfig = configuration; + m_serverAddress = configuration.value(config_key::hostName).toString().toNSString(); + + QString tunnelName; + if (configuration.value(config_key::description).toString().isEmpty()) { + tunnelName = QString("%1 %2") + .arg(configuration.value(config_key::hostName).toString()) + .arg(ProtocolProps::protoToString(proto)); + } + else { + tunnelName = QString("%1 (%2) %3") + .arg(configuration.value(config_key::description).toString()) + .arg(configuration.value(config_key::hostName).toString()) + .arg(ProtocolProps::protoToString(proto)); + } + + qDebug() << "IosController::connectVpn" << tunnelName; + + // reset m_currentTunnel + m_currentTunnel = nullptr; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block bool ok = true; + __block bool isNewTunnelCreated = false; + + [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray * _Nullable managers, NSError * _Nullable error) { + @try { + if (error) { + qDebug() << "IosController::connectVpn : Error:" << [error.localizedDescription UTF8String]; + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + ok = false; + return; + } + + NSInteger managerCount = managers.count; + qDebug() << "IosController::connectVpn : We have received managers:" << (long)managerCount; + + + for (NETunnelProviderManager *manager in managers) { + if ([manager.localizedDescription isEqualToString:tunnelName.toNSString()]) { + m_currentTunnel = manager; + qDebug() << "IosController::connectVpn : Using existing tunnel"; + if (manager.connection.status == NEVPNStatusConnected) { + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Connected); + return; + } + + break; + } + } + + if (!m_currentTunnel) { + qDebug() << "IosController::connectVpn : Creating new tunnel"; + isNewTunnelCreated = true; + m_currentTunnel = [[NETunnelProviderManager alloc] init]; + m_currentTunnel.localizedDescription = [NSString stringWithUTF8String:tunnelName.toStdString().c_str()]; + } + + } + @catch (NSException *exception) { + qDebug() << "IosController::connectVpn : exception" << QString::fromNSString(exception.reason); + ok = false; + m_currentTunnel = nullptr; + } + @finally { + dispatch_semaphore_signal(semaphore); + } + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if (!ok) return false; + + [[NSNotificationCenter defaultCenter] + removeObserver:(__bridge NSObject *)m_iosControllerWrapper]; + + [[NSNotificationCenter defaultCenter] + addObserver:(__bridge NSObject *)m_iosControllerWrapper + selector:@selector(vpnStatusDidChange:) + name:NEVPNStatusDidChangeNotification + object:m_currentTunnel.connection]; + + + if (proto == amnezia::Proto::OpenVpn) { + return setupOpenVPN(); + } + if (proto == amnezia::Proto::Cloak) { + return setupCloak(); + } + if (proto == amnezia::Proto::WireGuard) { + return setupWireGuard(); + } + + return false; +} + +void IosController::disconnectVpn() +{ + if (!m_currentTunnel) { + return; + } + + if ([m_currentTunnel.connection isKindOfClass:[NETunnelProviderSession class]]) { + [(NETunnelProviderSession *)m_currentTunnel.connection stopTunnel]; + } +} + + +void IosController::checkStatus() +{ + NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action]; + NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus]; + NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId]; + NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @""; + + NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue}; + sendVpnExtensionMessage(message, [&](NSDictionary* response){ + uint64_t txBytes = [response[@"tx_bytes"] intValue]; + uint64_t rxBytes = [response[@"rx_bytes"] intValue]; + emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes); + m_rxBytes = rxBytes; + m_txBytes = txBytes; + }); + +} + +void IosController::vpnStatusDidChange(void *pNotification) +{ + NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification; + + if (session /* && session == TunnelManager.session */ ) { + qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session; + emit connectionStateChanged(iosStatusToState(session.status)); + } +} + +void IosController::vpnConfigurationDidChange(void *pNotification) +{ + qDebug() << "IosController::vpnConfigurationDidChange" << pNotification; +} + +bool IosController::setupOpenVPN() +{ + qDebug() << "IosController::setupOpenVPN"; + + QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); + QString ovpnConfig = ovpn[config_key::config].toString(); + + return startOpenVPN(ovpnConfig); +} + +bool IosController::setupCloak() +{ + qDebug() << "IosController::setupCloak"; + m_serverAddress = @"127.0.0.1"; + QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); + QString ovpnConfig = ovpn[config_key::config].toString(); + + QJsonObject cloak = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Cloak)].toObject(); + + cloak["NumConn"] = 1; + if (cloak.contains("remote")) { + cloak["RemoteHost"] = cloak["remote"].toString(); + } + if (cloak.contains("port")) { + cloak["RemotePort"] = cloak["port"].toString(); + } + cloak.remove("remote"); + cloak.remove("port"); + cloak.remove("transport_proto"); + + QJsonObject jsonObject {}; + foreach(const QString& key, cloak.keys()) { + if(key == "NumConn" or key == "StreamTimeout"){ + jsonObject.insert(key, cloak.value(key).toInt()); + }else{ + jsonObject.insert(key, cloak.value(key).toString()); + } + } + QJsonDocument doc(jsonObject); + QString strJson(doc.toJson(QJsonDocument::Compact)); + QString cloakBase64 = strJson.toUtf8().toBase64(); + ovpnConfig.append("\n\n"); + ovpnConfig.append(cloakBase64); + ovpnConfig.append("\n\n"); + + return startOpenVPN(ovpnConfig); +} + +bool IosController::setupWireGuard() +{ + qDebug() << "IosController::setupWireGuard"; + QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject(); + + QString wgConfig = config[config_key::config].toString(); + + return startWireGuard(wgConfig); +} + +bool IosController::startOpenVPN(const QString &config) +{ + qDebug() << "IosController::startOpenVPN"; + + NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init]; + tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID]; + tunnelProtocol.providerConfiguration = @{@"ovpn": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]}; + tunnelProtocol.serverAddress = m_serverAddress; + + m_currentTunnel.protocolConfiguration = tunnelProtocol; + + startTunnel(); +} + +bool IosController::startWireGuard(const QString &config) +{ + qDebug() << "IosController::startWireGuard"; + + NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init]; + tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID]; + tunnelProtocol.providerConfiguration = @{@"wireguard": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]}; + tunnelProtocol.serverAddress = m_serverAddress; + + m_currentTunnel.protocolConfiguration = tunnelProtocol; + + startTunnel(); +} + +void IosController::startTunnel() +{ + m_rxBytes = 0; + m_txBytes = 0; + [m_currentTunnel setEnabled:YES]; + + [m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + qDebug() << "IosController::saveToPreferencesWithCompletionHandler called from thread:" << QThread::currentThread(); + + if (saveError) { + qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Save Error" << saveError.localizedDescription.UTF8String; + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + return; + } + + [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { + qDebug() << "IosController::loadFromPreferencesWithCompletionHandler called from thread:" << QThread::currentThread(); + + if (loadError) { + qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String; + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + return; + } + + NSError *startError = nil; + qDebug() << iosStatusToState(m_currentTunnel.connection.status); + + + NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action]; + NSString *actionValue = [NSString stringWithUTF8String:Action::start]; + NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId]; + NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @""; + + NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue}; + sendVpnExtensionMessage(message, [&](NSDictionary* response){ + qDebug() << "sendVpnExtensionMessage" << response; + }); + + + BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; + + if (!started || startError) { + qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error" + << (startError ? startError.localizedDescription.UTF8String : ""); + emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + } else { + qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded"; + } + }]; + }); + }]; +} + + +bool IosController::isOurManager(NETunnelProviderManager* manager) { + NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration; + + if (!tunnelProto) { + qDebug() << "Ignoring manager because the proto is invalid"; + return false; + } + + if (!tunnelProto.providerBundleIdentifier) { + qDebug() << "Ignoring manager because the bundle identifier is null"; + return false; + } + + if (![tunnelProto.providerBundleIdentifier isEqualToString:[NSString stringWithUTF8String:VPN_NE_BUNDLEID]]) { + qDebug() << "Ignoring manager because the bundle identifier doesn't match"; + return false; + } + + qDebug() << "Found the manager with the correct bundle identifier:" << QString::fromNSString(tunnelProto.providerBundleIdentifier); + + return true; +} + +void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function callback) +{ + if (!m_currentTunnel) { + qDebug() << "Cannot set an extension callback without a tunnel manager"; + return; + } + + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:message options:0 error:&error]; + + if (!data || error) { + qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:" + << [error.localizedDescription UTF8String]; + return; + } + + void (^completionHandler)(NSData *) = ^(NSData *responseData) { + if (!responseData) { + callback(nil); + return; + } + + NSError *deserializeError = nil; + NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&deserializeError]; + + if (response && [response isKindOfClass:[NSDictionary class]]) { + callback(response); + return; + } else if (deserializeError) { + qDebug() << "Failed to deserialize the VpnExtension response"; + } + + callback(nil); + }; + + NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection; + + NSError *sendError = nil; + + if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) { + [session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler]; + } else { + qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist"; + } + + if (sendError) { + qDebug() << "Failed to send message to VpnExtension. Error:" + << [sendError.localizedDescription UTF8String]; + } + +} diff --git a/client/platforms/ios/ios_controller_wrapper.h b/client/platforms/ios/ios_controller_wrapper.h new file mode 100644 index 00000000..e768821f --- /dev/null +++ b/client/platforms/ios/ios_controller_wrapper.h @@ -0,0 +1,15 @@ +#import +#import +#import + +class IosController; + +@interface IosControllerWrapper : NSObject { + IosController *cppController; +} + +- (instancetype)initWithCppController:(IosController *)controller; +- (void)vpnStatusDidChange:(NSNotification *)notification; +- (void)vpnConfigurationDidChange:(NSNotification *)notification; + +@end diff --git a/client/platforms/ios/ios_controller_wrapper.mm b/client/platforms/ios/ios_controller_wrapper.mm new file mode 100644 index 00000000..4bbe24dd --- /dev/null +++ b/client/platforms/ios/ios_controller_wrapper.mm @@ -0,0 +1,28 @@ +#import "ios_controller_wrapper.h" +#include "ios_controller.h" + +@implementation IosControllerWrapper + +- (instancetype)initWithCppController:(IosController *)controller { + self = [super init]; + if (self) { + cppController = controller; + } + return self; +} + +- (void)vpnStatusDidChange:(NSNotification *)notification { + + NETunnelProviderSession *session = (NETunnelProviderSession *)notification.object; + + if (session ) { + cppController->vpnStatusDidChange(session); + } +} + +- (void) vpnConfigurationDidChange:(NSNotification *)notification { + cppController->vpnStatusDidChange(notification); +} + + +@end diff --git a/client/platforms/ios/iosglue.mm b/client/platforms/ios/iosglue.mm index 854dfbfb..c37f625f 100644 --- a/client/platforms/ios/iosglue.mm +++ b/client/platforms/ios/iosglue.mm @@ -181,16 +181,8 @@ EXPORT bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN // Logging functions // ----------------- -#ifndef NETWORK_EXTENSION -namespace { -//Logger logger(LOG_IOS, "IOSSGlue"); -} -#endif EXPORT void write_msg_to_log(const char* tag, const char* msg) { -#ifndef NETWORK_EXTENSION -// logger.debug() << "Swift log - tag:" << tag << "msg: " << msg; -#else os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "tag: %s - msg: %s", tag, msg); @autoreleasepool { diff --git a/client/platforms/ios/iosiaphandler.h b/client/platforms/ios/iosiaphandler.h deleted file mode 100644 index 9ac3df80..00000000 --- a/client/platforms/ios/iosiaphandler.h +++ /dev/null @@ -1,30 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef IOSIAPHANDLER_H -#define IOSIAPHANDLER_H - -#include "iaphandler.h" - -class IOSIAPHandler final : public IAPHandler { - Q_OBJECT - Q_DISABLE_COPY_MOVE(IOSIAPHandler) - - public: - explicit IOSIAPHandler(QObject* parent); - ~IOSIAPHandler(); - - public slots: - void productRegistered(void* product); - void processCompletedTransactions(const QStringList& ids); - - protected: - void nativeRegisterProducts() override; - void nativeStartSubscription(Product* product) override; - - private: - void* m_delegate = nullptr; -}; - -#endif // IOSIAPHANDLER_H diff --git a/client/platforms/ios/ioslogger.swift b/client/platforms/ios/ioslogger.swift index f6fbe866..dcedc2bd 100644 --- a/client/platforms/ios/ioslogger.swift +++ b/client/platforms/ios/ioslogger.swift @@ -17,7 +17,12 @@ public class Logger { deinit {} func log(message: String) { - write_msg_to_log(tag, message.trimmingCharacters(in: .newlines)) + let suiteName = [NSString stringWithUTF8String:GROUP_ID] + let logKey = "logMessages" + let sharedDefaults = UserDefaults(suiteName: suiteName) + var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? [] + logs.append(message) + sharedDefaults?.set(logs, forKey: logKey) } func writeLog(to targetFile: String) -> Bool { @@ -37,8 +42,7 @@ public class Logger { appVersion += " (\(appBuild))" } - let goBackendVersion = "1" - Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)") + Logger.global?.log(message: "App version: \(appVersion)") } } @@ -50,5 +54,4 @@ func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { func wg_log(_ type: OSLogType, message msg: String) { os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg) Logger.global?.log(message: msg) - NSLog("AMNEZIA: \(msg)") } diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 69b78558..41d835ae 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -3,7 +3,7 @@ import NetworkExtension import os import Darwin import OpenVPNAdapter -//import Tun2socks + enum TunnelProtoType: String { case wireguard, openvpn, shadowsocks, none } @@ -11,25 +11,19 @@ enum TunnelProtoType: String { struct Constants { static let kDefaultPathKey = "defaultPath" static let processQueueName = "org.amnezia.process-packets" - static let ssQueueName = "org.amnezia.shadowsocks" static let kActivationAttemptId = "activationAttemptId" static let ovpnConfigKey = "ovpn" - static let ssConfigKey = "ss" + static let wireGuardConfigKey = "wireguard" static let loggerTag = "NET" - static let ssRemoteHost = "server" - static let ssRemotePort = "server_port" - static let ssLocalAddressKey = "local_addr" - static let ssLocalPortKey = "local_port" - static let ssTimeoutKey = "timeout" - static let ssCipherKey = "method" - static let ssPasswordKey = "password" + static let kActionStart = "start" static let kActionRestart = "restart" static let kActionStop = "stop" static let kActionGetTunnelId = "getTunnelId" + static let kActionStatus = "status" static let kActionIsServerReachable = "isServerReachable" static let kMessageKeyAction = "action" - static let kMessageKeyTunnelid = "tunnelId" + static let kMessageKeyTunnelId = "tunnelId" static let kMessageKeyConfig = "config" static let kMessageKeyErrorCode = "errorCode" static let kMessageKeyHost = "host" @@ -37,8 +31,6 @@ struct Constants { static let kMessageKeyOnDemand = "is-on-demand" } -typealias ShadowsocksProxyCompletion = ((Int32, NSError?) -> Void)? - class PacketTunnelProvider: NEPacketTunnelProvider { private lazy var wgAdapter: WireGuardAdapter = { @@ -46,90 +38,128 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wg_log(logLevel.osLogLevel, message: message) } }() - + private lazy var ovpnAdapter: OpenVPNAdapter = { let adapter = OpenVPNAdapter() adapter.delegate = self return adapter }() - private var shadowSocksConfig: Data? = nil - private var openVPNConfig: Data? = nil - var ssCompletion: ShadowsocksProxyCompletion = nil + /// Internal queue. + private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility) -// private var ssProvider: ShadowSocksTunnel? = nil -// private var ssLocalPort: Int = 8585 -// private var ssRemoteHost = "" -// private var leafProvider: TunProvider? = nil -// -// private var tun2socksTunnel: Tun2socksOutlineTunnelProtocol? = nil -// private var tun2socksWriter: Tun2socksTunWriter? = nil -// private let processQueue = DispatchQueue(label: Constants.processQueueName) -// private var connection: NWTCPConnection? = nil -// private var session: NWUDPSession? = nil -// private var observer: AnyObject? - + private var openVPNConfig: Data? = nil + let vpnReachability = OpenVPNReachability() var startHandler: ((Error?) -> Void)? var stopHandler: (() -> Void)? - var protoType: TunnelProtoType = .wireguard + var protoType: TunnelProtoType = .none - override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - let activationAttemptId = options?[Constants.kActivationAttemptId] as? String - let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) - + override init() { Logger.configureGlobal(tagged: Constants.loggerTag, withFilePath: FileManager.logFileURL?.path) - - if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, - let providerConfiguration = protocolConfiguration.providerConfiguration, - let _: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data { - let withoutShadowSocks = providerConfiguration[Constants.ssConfigKey] as? Data == nil - protoType = withoutShadowSocks ? .openvpn : .shadowsocks - } else { - protoType = .wireguard + Logger.global?.log(message: "Init NEPacketTunnelProvider") + super.init() + } + + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else { + Logger.global?.log(message: "Failed to serialize message from app") + return + } + + guard let completionHandler = completionHandler else { + Logger.global?.log(message: "Missing message completion handler") + return + } + + guard let action = message[Constants.kMessageKeyAction] as? String else { + Logger.global?.log(message: "Missing action key in app message") + completionHandler(nil) + return + } + + if action == Constants.kActionStatus { + handleStatusAppMessage(messageData, completionHandler: completionHandler) } - switch protoType { - case .wireguard: - startWireguard(activationAttemptId: activationAttemptId, - errorNotifier: errorNotifier, - completionHandler: completionHandler) - case .openvpn: - startOpenVPN(completionHandler: completionHandler) - case .shadowsocks: - break -// startShadowSocks(completionHandler: completionHandler) - case .none: - break + let callbackWrapper: (NSNumber?) -> Void = { errorCode in + //let tunnelId = self.tunnelConfig?.id ?? "" + let response: [String: Any] = [ + Constants.kMessageKeyAction: action, + Constants.kMessageKeyErrorCode: errorCode ?? NSNull(), + Constants.kMessageKeyTunnelId: 0 + ] + + completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) + } + } + + override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { + dispatchQueue.async { + let activationAttemptId = options?[Constants.kActivationAttemptId] as? String + let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) + + Logger.global?.log(message: "PacketTunnelProvider startTunnel") + + if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol { + let providerConfiguration = protocolConfiguration.providerConfiguration + if let _: Data = providerConfiguration?[Constants.ovpnConfigKey] as? Data { + self.protoType = .openvpn + } + else if let _: Data = providerConfiguration?[Constants.wireGuardConfigKey] as? Data { + self.protoType = .wireguard + } + } + else { + self.protoType = .none + } + + switch self.protoType { + case .wireguard: + self.startWireguard(activationAttemptId: activationAttemptId, + errorNotifier: errorNotifier, + completionHandler: completionHandler) + case .openvpn: + self.startOpenVPN(completionHandler: completionHandler) + case .shadowsocks: + break + // startShadowSocks(completionHandler: completionHandler) + case .none: + break + } } } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - switch protoType { - case .wireguard: - stopWireguard(with: reason, completionHandler: completionHandler) - case .openvpn: - stopOpenVPN(with: reason, completionHandler: completionHandler) - case .shadowsocks: - break -// stopShadowSocks(with: reason, completionHandler: completionHandler) - case .none: - break + dispatchQueue.async { + + switch self.protoType { + case .wireguard: + self.stopWireguard(with: reason, completionHandler: completionHandler) + case .openvpn: + self.stopOpenVPN(with: reason, completionHandler: completionHandler) + case .shadowsocks: + break + // stopShadowSocks(with: reason, completionHandler: completionHandler) + case .none: + break + } } } - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { switch protoType { case .wireguard: - handleWireguardAppMessage(messageData, completionHandler: completionHandler) + handleWireguardStatusMessage(messageData, completionHandler: completionHandler) case .openvpn: - handleOpenVPNAppMessage(messageData, completionHandler: completionHandler) + handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler) case .shadowsocks: break // handleShadowSocksAppMessage(messageData, completionHandler: completionHandler) case .none: break + } } @@ -137,48 +167,56 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private func startWireguard(activationAttemptId: String?, errorNotifier: ErrorNotifier, completionHandler: @escaping (Error?) -> Void) { - guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol, - let tunnelConfiguration = tunnelProviderProtocol.asTunnelConfiguration() else { - errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid) - completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid) - return - } + guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, + let providerConfiguration = protocolConfiguration.providerConfiguration, + let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else { + wg_log(.error, message: "Can't start WireGuard config missing") + completionHandler(nil) + return + } + + let wgConfigStr = String(data: wgConfig, encoding: .utf8)! + + guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr) else { + wg_log(.error, message: "Can't parse WireGuard config") + completionHandler(nil) + return + } + wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app")) // Start the tunnel wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in guard let adapterError = adapterError else { let interfaceName = self.wgAdapter.interfaceName ?? "unknown" - wg_log(.info, message: "Tunnel interface is \(interfaceName)") - completionHandler(nil) return } - + switch adapterError { case .cannotLocateTunnelFileDescriptor: wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor") errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor) completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor) - + case .dnsResolution(let dnsErrors): let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address } .joined(separator: ", ") wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)") errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure) completionHandler(PacketTunnelProviderError.dnsResolutionFailure) - + case .setNetworkSettings(let error): wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)") errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings) completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings) - + case .startWireGuardBackend(let errorCode): wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)") errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend) completionHandler(PacketTunnelProviderError.couldNotStartBackend) - + case .invalidState: // Must never happen fatalError() @@ -197,35 +235,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider { setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler) } -/* - private func startShadowSocks(completionHandler: @escaping (Error?) -> Void) { - guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, - let providerConfiguration = protocolConfiguration.providerConfiguration, - let ssConfiguration: Data = providerConfiguration[Constants.ssConfigKey] as? Data, - let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else { - // TODO: handle errors properly - wg_log(.error, message: "Cannot start startShadowSocks()") - return - } - self.shadowSocksConfig = ssConfiguration - self.openVPNConfig = ovpnConfiguration - wg_log(.info, message: "Prepare to start shadowsocks/tun2socks/leaf") -// self.startSSProvider(completion: completionHandler) -// startTun2SocksTunnel(completion: completionHandler) - self.startLeafRedirector(completion: completionHandler) - } -*/ + private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { wg_log(.info, staticMessage: "Stopping tunnel") wgAdapter.stop { error in ErrorNotifier.removeLastErrorFile() - + if let error = error { wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)") } completionHandler() - + #if os(macOS) // HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107). // Remove it when they finally fix this upstream and the fix has been rolled out to @@ -242,16 +263,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } ovpnAdapter.disconnect() } -/* - private func stopShadowSocks(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - stopOpenVPN(with: reason) { [weak self] in - guard let `self` = self else { return } -// self.stopSSProvider(completionHandler: completionHandler) -// self.stopTun2SocksTunnel(completionHandler: completionHandler) - self.stopLeafRedirector(completion: completionHandler) + + func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + guard let completionHandler = completionHandler else { return } + wgAdapter.getRuntimeConfiguration { settings in + var data: Data? + if let settings = settings { + data = settings.data(using: .utf8)! + } + + let components = settings!.components(separatedBy: "\n") + + var settingsDictionary: [String: String] = [:] + for component in components{ + let pair = component.components(separatedBy: "=") + if pair.count == 2 { + settingsDictionary[pair[0]] = pair[1] + } + } + + let response: [String: Any] = [ + "rx_bytes" : settingsDictionary["rx_bytes"] ?? "0", + "tx_bytes" : settingsDictionary["tx_bytes"] ?? "0" + ] + + completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) } } -*/ + private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } if messageData.count == 1 && messageData[0] == 0 { @@ -270,7 +309,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + do { let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString) wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in @@ -279,7 +318,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) return } - + self.wgAdapter.getRuntimeConfiguration { settings in var data: Data? if let settings = settings { @@ -295,399 +334,25 @@ class PacketTunnelProvider: NEPacketTunnelProvider { completionHandler(nil) } } - - private func handleOpenVPNAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { + + private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { guard let completionHandler = completionHandler else { return } - if messageData.count == 1 && messageData[0] == 0 { let bytesin = ovpnAdapter.transportStatistics.bytesIn - let strBytesin = "rx_bytes=" + String(bytesin); - let bytesout = ovpnAdapter.transportStatistics.bytesOut - let strBytesout = "tx_bytes=" + String(bytesout); + + let response: [String: Any] = [ + "rx_bytes" : bytesin, + "tx_bytes" : bytesout + ] - let strData = strBytesin + "\n" + strBytesout; - let data = Data(strData.utf8) - completionHandler(data) - } + completionHandler(try? JSONSerialization.data(withJSONObject: response, options: [])) } - -/* - private func handleShadowSocksAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { - guard let completionHandler = completionHandler else { return } - if let configString = String(data: messageData, encoding: .utf8) { - wg_log(.debug, message: configString) - } - - completionHandler(messageData) - } -*/ - // MARK: -- Tun2sock provider methods -/* - private func startTun2SocksTunnel(completion: @escaping (Error?) -> Void) { - guard let ssConfiguration = self.shadowSocksConfig, - let ovpnConfiguration = self.openVPNConfig, - let ssConfig = try? JSONSerialization.jsonObject(with: ssConfiguration, options: []) as? [String: Any] - else { - wg_log(.info, message: "Cannot parse shadowsocks config") - let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(tun2socksError) - return - } - wg_log(.info, message: "SS Config: \(ssConfig)") - - guard let remoteHost = ssConfig[Constants.ssRemoteHost] as? String, - let remotePort = ssConfig[Constants.ssRemotePort] as? Int, - let method = ssConfig[Constants.ssCipherKey] as? String, - let password = ssConfig[Constants.ssPasswordKey] as? String else { - wg_log(.error, message: "Cannot parse ss config") - let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(tun2socksError) - return - } - - let connError: AutoreleasingUnsafeMutablePointer? = nil - ShadowsocksCheckConnectivity(remoteHost, remotePort, password, method, nil, connError) - if (connError?.pointee != nil) { - wg_log(.error, message: "Failed to start tun2socks tunnel with error: \(connError?.pointee?.localizedDescription ?? "oops")") - let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(tun2socksError) - return - } - setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, withShadowSocks: true) { vpnError in - guard vpnError == nil else { - wg_log(.error, message: "Failed to start openvpn with tun2socks tunnel with error: \(vpnError?.localizedDescription ?? "oops")") - let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(tun2socksError) - return - } -// let ipv4settings: NEIPv4Settings = .init(addresses: ["192.0.2.1"], subnetMasks: ["255.255.255.0"]) -// ipv4settings.includedRoutes = [.default()] -// ipv4settings.excludedRoutes = [] -// -// let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"]) -// let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "192.0.2.2") -// settings.ipv4Settings = ipv4settings -// settings.dnsSettings = dnsSettings -// settings.mtu = 1600 -// -// setTunnelNetworkSettings(settings) { tunError in - let ifaces = Interface.allInterfaces() - .filter { $0.family == .ipv4 } - .map { iface in iface.name } - - wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") - self.tun2socksWriter = Tun2socksTunWriter() - let tunError: AutoreleasingUnsafeMutablePointer? = nil - self.tun2socksTunnel = Tun2socksConnectShadowsocksTunnel(self.tun2socksWriter, remoteHost, remotePort, password, method, false, tunError) - if (tunError?.pointee != nil) { - wg_log(.error, message: "Failed to start tun2socks tunnel with error: \(tunError?.pointee?.localizedDescription ?? "oops")") - let tun2socksError: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(tun2socksError) - return - } - self.processQueue.async { self.processPackets() } - completion(nil) - } - } - - private func stopTun2SocksTunnel(completionHandler: @escaping () -> Void) { - if self.tun2socksTunnel != nil && self.tun2socksTunnel!.isConnected() { - self.tun2socksTunnel?.disconnect() - } - try? self.tun2socksWriter?.close() - completionHandler() - } - - private func processPackets() { - packetFlow.readPacketObjects { [weak self] packets in - guard let `self` = self else { return } - do { - let _ = try packets.map { - var bytesWritten: Int = 0 - try self.tun2socksTunnel?.write($0.data, ret0_: &bytesWritten) - self.processQueue.async { - self.processPackets() - } - } - } catch (let err) { - wg_log(.debug, message: "Error in tun2sock: \(err.localizedDescription)") - } - } - } - // MARK: -- Leaf provider methods - private func prepareConfig(onInterface iface: String, fromSSConfig ssConfig: Data, andOvpnConfig ovpnConfig: Data) -> UnsafePointer? { - guard let ssConfig = try? JSONSerialization.jsonObject(with: ssConfig, options: []) as? [String: Any] else { - self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", - code: 100, - userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) - return nil - } - guard let remoteHost = ssConfig[Constants.ssRemoteHost] as? String, - let remotePort = ssConfig[Constants.ssRemotePort] as? Int, - let method = ssConfig[Constants.ssCipherKey] as? String, - let password = ssConfig[Constants.ssPasswordKey] as? String else { - self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", - code: 100, - userInfo: [NSLocalizedDescriptionKey: "Cannot assign profile params for ss in tunnel"])) - return nil - } - var insettings: [String: Any] = .init() - insettings["name"] = iface - insettings["address"] = "127.0.0.2" - insettings["netmask"] = "255.255.255.0" - insettings["gateway"] = "127.0.0.1" - insettings["mtu"] = 1600 - var inbounds: [String: Any] = .init() - inbounds["protocol"] = "tun" - inbounds["settings"] = insettings - inbounds["tag"] = "tun_in" - var outbounds: [String: Any] = .init() - var outsettings: [String: Any] = .init() - outsettings["address"] = remoteHost - outsettings["port"] = remotePort - outsettings["method"] = method - outsettings["password"] = password - outbounds["protocol"] = "shadowsocks" - outbounds["settings"] = outsettings - outbounds["tag"] = "shadowsocks_out" - var params: [String: Any] = .init() - params["inbounds"] = [inbounds] - params["outbounds"] = [outbounds] - wg_log(.error, message: "Config dictionary: \(params)") - guard let jsonData = try? JSONSerialization.data(withJSONObject: params, options: .prettyPrinted), - let jsonString = String(data: jsonData, encoding: .utf8) else { return nil } - wg_log(.error, message: "JSON String: \(jsonString)") - var path = "" - if let documentDirectory = FileManager.default.urls(for: .documentDirectory, - in: .userDomainMask).first { - let pathWithFilename = documentDirectory.appendingPathComponent("config.json") - do { - try jsonString.write(to: pathWithFilename, - atomically: true, - encoding: .utf8) - path = pathWithFilename.path - } catch { - // Handle error - } - } - - return UnsafePointer(strdup(path)) - } - - private func startLeafRedirector(completion: @escaping (Error?) -> Void) { - let ipv4settings: NEIPv4Settings = .init(addresses: ["127.0.0.2"], subnetMasks: ["255.255.255.0"]) - ipv4settings.includedRoutes = [.default()] - ipv4settings.excludedRoutes = [] - - let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"]) - dnsSettings.matchDomains = [] - let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "127.0.0.1") - settings.ipv4Settings = ipv4settings - settings.dnsSettings = dnsSettings - settings.mtu = 1600 - - self.setTunnelNetworkSettings(settings) { tunError in - let ifaces = Interface.allInterfaces() - .filter { $0.name.contains("tun") && $0.family == .ipv4 } - .map { iface in iface.name } - wg_log(.error, message: "Try on interface: \(ifaces)") - guard let ssConf = self.shadowSocksConfig, - let ovpnConf = self.openVPNConfig, - let config = self.prepareConfig(onInterface: ifaces.first ?? "utun2", - fromSSConfig: ssConf, - andOvpnConfig: ovpnConf) else { - let ret: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(ret) - return - } - self.leafProvider = TunProvider(withConfig: config) - self.leafProvider?.testConfig(onPath: config) { configError in - wg_log(.error, message: "Config check status: \(configError!.desc)") - guard configError! == .noError else { - wg_log(.error, message: "Config check status: \(configError!.desc)") - let ret: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(ret) - return - } - - wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") - - self.leafProvider?.startTunnel { tunError in - wg_log(.error, message: "Leaf tunnel start status: \(tunError!.desc)") - guard tunError! == .noError else { - wg_log(.error, message: "Leaf tunnel start error: \(tunError!.desc)") - let ret: NSError = .init(domain: "", code: 100, userInfo: nil) - completion(ret) - return - } - completion(nil) - } - } - - } - } - private func stopLeafRedirector(completion: @escaping () -> Void) { - leafProvider?.stopTunnel { error in - // TODO: handle errors - completion() - } - } - - // MARK: -- ShadowSocks Provider methods - - private func startSSProvider(completion: @escaping (Error?) -> Void) { - guard let ssConfig = self.shadowSocksConfig, let ovpnConfig = self.openVPNConfig else { return } - if ssProvider == nil { - guard let config = try? JSONSerialization.jsonObject(with: ssConfig, options: []) as? [String: Any], - let remoteHost = config[Constants.ssRemoteHost] as? String, - let port = config[Constants.ssLocalPortKey] as? Int else { - self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", - code: 100, - userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) - return - } - ssProvider = SSProvider(config: ssConfig, localPort: port) - ssLocalPort = port - ssRemoteHost = remoteHost - } - - ssProvider?.start(usingPacketFlow: packetFlow, withConnectivityCheck: false) { errorCode in - wg_log(.info, message: "After starting shadowsocks") - wg_log(.error, message: "Starting ShadowSocks State: \(String(describing: errorCode))") - if (errorCode != nil && errorCode! != .noError) { - wg_log(.error, message: "Error starting ShadowSocks: \(String(describing: errorCode))") - return - } -// self.setupAndHandleOpenVPNOverSSConnection(withConfig: ovpnConfig) - self.startAndHandleTunnelOverSS(completionHandler: completion) - } - } - - private func startAndHandleTunnelOverSS(completionHandler: @escaping (Error?) -> Void) { -// let ipv4settings: NEIPv4Settings = .init(addresses: ["192.0.2.2"], subnetMasks: ["255.255.255.0"]) -// let addedRoute1 = NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "0.0.0.0") -// addedRoute1.gatewayAddress = "192.0.2.1" -// ipv4settings.includedRoutes = [addedRoute1] -// ipv4settings.excludedRoutes = [] -// -// let dnsSettings: NEDNSSettings = .init(servers: ["1.1.1.1", "9.9.9.9", "208.67.222.222", "208.67.220.220"]) -// let settings: NEPacketTunnelNetworkSettings = .init(tunnelRemoteAddress: "192.0.2.1") -// settings.ipv4Settings = ipv4settings -// settings.dnsSettings = dnsSettings -// settings.mtu = 1600 -// -// setTunnelNetworkSettings(settings) { tunError in -// -// } - - let ifaces = Interface.allInterfaces() - .filter { $0.family == .ipv4 } - .map { iface in iface.name } - - wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") - let endpoint = NWHostEndpoint(hostname: "127.0.0.1", port: "\(self.ssLocalPort)") - self.session = self.createUDPSession(to: endpoint, from: nil) - self.setupWriteToFlow() - self.observer = self.session!.observe(\.state, options: [.new]) { conn, _ in - switch conn.state { - case .ready: - self.readFromFlow() - completionHandler(nil) - case .cancelled, .failed, .invalid: - self.stopSSProvider { - self.cancelTunnelWithError(nil) - completionHandler(nil) - } - default: - break - } - } - } - - private func setupAndHandleOpenVPNOverSSConnection(withConfig ovpnConfig: Data) { - let endpoint = NWHostEndpoint(hostname: "127.0.0.1", port: "\(self.ssLocalPort)") - self.session = self.createUDPSession(to: endpoint, from: nil) -// self.connection = self.createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil) - self.observer = self.session!.observe(\.state, options: [.new]) { conn, _ in - switch conn.state { - case .ready: - self.processQueue.async { - self.setupWriteToFlow() - } - self.processQueue.async { - self.readFromFlow() - } - - self.setupAndlaunchOpenVPN(withConfig: ovpnConfig, withShadowSocks: true) { vpnError in - wg_log(.info, message: "After starting openVPN") - guard vpnError == nil else { - wg_log(.error, message: "Failed to start openvpn with error: \(vpnError?.localizedDescription ?? "oops")") - return - } - } - case .cancelled, .failed, .invalid: - self.stopSSProvider { - self.cancelTunnelWithError(nil) - } - default: - break - } - } - } - - private func readFromFlow() { - wg_log(.error, message: "Start reading packets to connection") - wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")") - packetFlow.readPackets { [weak self] packets, protocols in - wg_log(.error, message: "\(packets.count) outcoming packets processed of \(protocols.first?.stringValue ?? "unknown") type") - guard let `self` = self else { return } - self.session?.writeMultipleDatagrams(packets, completionHandler: { _ in - self.processQueue.async { - self.readFromFlow() - } - }) -// let _ = packets.map { -// wg_log(.error, message: "Packet: \($0.data) of \($0.protocolFamily)") -// self.connection?.write($0.data, completionHandler: { _ in }) -// self.processQueue.async { -// self.readFromFlow() -// } -// } - } - } - - private func setupWriteToFlow() { - wg_log(.error, message: "Start writing packets from connection") - wg_log(.error, message: "Connection is \(session != nil ? "not null" : "null")") - session?.setReadHandler({ ssdata, error in - wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null"), error: \(error?.localizedDescription ?? "none")") - guard error == nil, let packets = ssdata else { return } - wg_log(.error, message: "\(packets.count) incoming packets processed") - self.packetFlow.writePackets(packets, withProtocols: [NSNumber(value: AF_INET)]) - }, maxDatagrams: Int.max) - -// connection?.readLength(1450, completionHandler: { [weak self] ssdata, readError in -// wg_log(.error, message: "Packets are \(ssdata != nil ? "not null" : "null")") -// guard let `self` = self, let packets = ssdata else { return } -// wg_log(.error, message: "Packet: \(packets) or error: \(readError?.localizedDescription ?? "")") -// self.packetFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)]) -// self.processQueue.async { -// self.writeToFlow() -// } -// }) - } - - private func stopSSProvider(completionHandler: @escaping () -> Void) { - self.ssProvider?.stop { _ in - if let provider = self.ssProvider, let threadId = provider.ssLocalThreadId { - pthread_kill(threadId, SIGUSR1) - } - self.ssProvider = nil - completionHandler() - } - } -*/ + + + // TODO review private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) { + wg_log(.info, message: "setupAndlaunchOpenVPN") + let str = String(decoding: ovpnConfiguration, as: UTF8.self) let configuration = OpenVPNConfiguration() @@ -699,53 +364,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let evaluation: OpenVPNConfigurationEvaluation do { evaluation = try ovpnAdapter.apply(configuration: configuration) - + } catch { completionHandler(error) return } - + if !evaluation.autologin { wg_log(.info, message: "Implement login with user credentials") } - + vpnReachability.startTracking { [weak self] status in guard status == .reachableViaWiFi else { return } self?.ovpnAdapter.reconnect(afterTimeInterval: 5) } - + startHandler = completionHandler ovpnAdapter.connect(using: packetFlow) - + // let ifaces = Interface.allInterfaces() // .filter { $0.family == .ipv4 } // .map { iface in iface.name } - + // wg_log(.error, message: "Available TUN Interfaces: \(ifaces)") } - + // MARK: -- Network observing methods - - private func getSockPort(for fd: Int32) -> Int32 { - var addr_in = sockaddr_in(); - addr_in.sin_len = UInt8(MemoryLayout.size(ofValue: addr_in)); - addr_in.sin_family = sa_family_t(AF_INET); - - var len = socklen_t(addr_in.sin_len); - let result = withUnsafeMutablePointer(to: &addr_in, { - $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { - return Darwin.getsockname(fd, $0, &len); - } - }); - - if result == 0 { - return Int32(addr_in.sin_port); - } else { - wg_log(.error, message: "getSockPort(\(fd)) error: \(String(describing: strerror(errno)))") - return 0 - } - } - + private func startListeningForNetworkChanges() { stopListeningForNetworkChanges() addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil) @@ -779,23 +424,43 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wg_log(.info, message: "Tunnel restarted.") startTunnel(options: nil, completionHandler: completion) } -} -extension WireGuardLogLevel { - var osLogLevel: OSLogType { - switch self { - case .verbose: - return .debug - case .error: - return .error + private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) { + dispatchPrecondition(condition: .onQueue(dispatchQueue)) + + let emptyTunnelConfiguration = TunnelConfiguration( + name: nil, + interface: InterfaceConfiguration(privateKey: PrivateKey()), + peers: [] + ) + + wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in + self.dispatchQueue.async { + if let error { + Logger.global?.log(message: "Failed to start an empty tunnel") + completionHandler(error) + } else { + Logger.global?.log(message: "Started an empty tunnel") + self.tunnelAdapterDidStart() + } + } } + + let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1") + + self.setTunnelNetworkSettings(settings) { error in + completionHandler(error) + } + } + + private func tunnelAdapterDidStart() { + dispatchPrecondition(condition: .onQueue(dispatchQueue)) + // ... } } extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {} -/* extension NEPacketTunnelFlow: ShadowSocksAdapterPacketFlow {} */ - extension PacketTunnelProvider: OpenVPNAdapterDelegate { // OpenVPNAdapter calls this delegate method to configure a VPN tunnel. @@ -873,14 +538,14 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate { wg_log(.info, message: logMessage) } } -/* -extension PacketTunnelProvider: Tun2socksTunWriterProtocol { - func write(_ p0: Data?, n: UnsafeMutablePointer?) throws { - if let packets = p0 { - self.packetFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)]) + +extension WireGuardLogLevel { + var osLogLevel: OSLogType { + switch self { + case .verbose: + return .debug + case .error: + return .error } } - - func close() throws {} } -*/ diff --git a/client/platforms/ios/iosvpnprotocol.swift b/client/platforms/ios/iosvpnprotocol.swift deleted file mode 100644 index 96f4675b..00000000 --- a/client/platforms/ios/iosvpnprotocol.swift +++ /dev/null @@ -1,732 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import NetworkExtension - -let vpnName = "Amnezia WireguardVPN" -var vpnBundleID = ""; - -@objc class VPNIPAddressRange : NSObject { - public var address: NSString = "" - public var networkPrefixLength: UInt8 = 0 - public var isIpv6: Bool = false - - @objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) { - super.init() - - self.address = address - self.networkPrefixLength = networkPrefixLength - self.isIpv6 = isIpv6 - } -} - -public class IOSVpnProtocolImpl : NSObject { - - private var tunnel: NETunnelProviderManager? = nil - private var stateChangeCallback: ((Bool) -> Void?)? = nil - private var privateKey : PrivateKey? = nil - private var deviceIpv4Address: String? = nil - private var deviceIpv6Address: String? = nil - private var openVPNConfig: String? = nil - private var shadowSocksConfig: String? = nil - - @objc enum ConnectionState: Int { case Error, Connected, Disconnected } - - @objc init(bundleID: String, - config: String, - closure: @escaping (ConnectionState, Date?) -> Void, - callback: @escaping (Bool) -> Void) { - super.init() - Logger.configureGlobal(tagged: "APP", withFilePath: "") - - vpnBundleID = bundleID; - precondition(!vpnBundleID.isEmpty) - - stateChangeCallback = callback - self.openVPNConfig = config - self.shadowSocksConfig = nil - - NotificationCenter.default.removeObserver(self) - NotificationCenter.default.addObserver(self, - selector: #selector(self.vpnStatusDidChange(notification:)), - name: Notification.Name.NEVPNStatusDidChange, - object: nil) - - NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in - if let error = error { - Logger.global?.log(message: "Loading from preference failed: \(error)") - closure(ConnectionState.Error, nil) - return - } - - if self == nil { - Logger.global?.log(message: "We are shutting down.") - return - } - - let nsManagers = managers ?? [] - Logger.global?.log(message: "We have received \(nsManagers.count) managers.") - print("We have received \(nsManagers.count) managers.") - - let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:)) - -// if let name = tunnel?.localizedDescription, name == vpnName { -// tunnel?.removeFromPreferences(completionHandler: { removeError in -// if let error = removeError { -// Logger.global?.log(message: "WireguardVPN Tunnel Remove from Prefs Error: \(error)") -// closure(ConnectionState.Error, nil) -// return -// } -// }) -// } - - if tunnel == nil { - Logger.global?.log(message: "Creating the tunnel") - print("Creating the tunnel") - self!.tunnel = NETunnelProviderManager() - closure(ConnectionState.Disconnected, nil) - return - } - - Logger.global?.log(message: "Tunnel already exists") - print("Tunnel already exists") - - self!.tunnel = tunnel - if tunnel?.connection.status == .connected { - closure(ConnectionState.Connected, tunnel?.connection.connectedDate) - } else { - closure(ConnectionState.Disconnected, nil) - } - } - } - - @objc init(bundleID: String, - privateKey: Data, - deviceIpv4Address: String, - deviceIpv6Address: String, - closure: @escaping (ConnectionState, Date?) -> Void, - callback: @escaping (Bool) -> Void) { - super.init() - - Logger.configureGlobal(tagged: "APP", withFilePath: "") - - vpnBundleID = bundleID; - precondition(!vpnBundleID.isEmpty) - - stateChangeCallback = callback - self.privateKey = PrivateKey(rawValue: privateKey) - self.deviceIpv4Address = deviceIpv4Address - self.deviceIpv6Address = deviceIpv6Address - self.openVPNConfig = nil - self.shadowSocksConfig = nil - - NotificationCenter.default.removeObserver(self) - - NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil) - - NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in - if let error = error { - Logger.global?.log(message: "Loading from preference failed: \(error)") - closure(ConnectionState.Error, nil) - return - } - - if self == nil { - Logger.global?.log(message: "We are shutting down.") - return - } - - let nsManagers = managers ?? [] - Logger.global?.log(message: "We have received \(nsManagers.count) managers.") - print("We have received \(nsManagers.count) managers.") - - let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:)) - -// if let name = tunnel?.localizedDescription, name != vpnName { -// tunnel?.removeFromPreferences(completionHandler: { removeError in -// if let error = removeError { -// Logger.global?.log(message: "OpenVpn Tunnel Remove from Prefs Error: \(error)") -// closure(ConnectionState.Error, nil) -// return -// } -// }) -// } - - if tunnel == nil { - Logger.global?.log(message: "Creating the tunnel") - print("Creating the tunnel") - self!.tunnel = NETunnelProviderManager() - closure(ConnectionState.Disconnected, nil) - return - } - - Logger.global?.log(message: "Tunnel already exists") - print("Tunnel already exists") - - self!.tunnel = tunnel - - if tunnel?.connection.status == .connected { - closure(ConnectionState.Connected, tunnel?.connection.connectedDate) - } else { - closure(ConnectionState.Disconnected, nil) - } - } - } - - @objc init(bundleID: String, - tunnelConfig: String, - ssConfig: String, - closure: @escaping (ConnectionState, Date?) -> Void, - callback: @escaping (Bool) -> Void) { - super.init() - - vpnBundleID = bundleID; - precondition(!vpnBundleID.isEmpty) - - stateChangeCallback = callback - self.openVPNConfig = tunnelConfig - self.shadowSocksConfig = ssConfig - - NotificationCenter.default.removeObserver(self) - - NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil) - - NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in - if let error = error { - Logger.global?.log(message: "Loading from preference failed: \(error)") - closure(ConnectionState.Error, nil) - return - } - - if self == nil { - Logger.global?.log(message: "We are shutting down.") - return - } - - let nsManagers = managers ?? [] - Logger.global?.log(message: "We have received \(nsManagers.count) managers.") - print("We have received \(nsManagers.count) managers.") - - let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:)) - - if tunnel == nil { - Logger.global?.log(message: "Creating the tunnel via shadowsocks") - print("Creating the tunnel via SS") - self!.tunnel = NETunnelProviderManager() - closure(ConnectionState.Disconnected, nil) - return - } - - Logger.global?.log(message: "Tunnel already exists") - print("SS Tunnel already exists") - - self!.tunnel = tunnel - - if tunnel?.connection.status == .connected { - closure(ConnectionState.Connected, tunnel?.connection.connectedDate) - } else { - closure(ConnectionState.Disconnected, nil) - } - } - } - - @objc private func vpnStatusDidChange(notification: Notification) { - guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return } - - switch session.status { - case .connected: - Logger.global?.log(message: "STATE CHANGED: connected") - print("STATE CHANGED: connected") - case .connecting: - Logger.global?.log(message: "STATE CHANGED: connecting") - print("STATE CHANGED: connecting") - case .disconnected: - Logger.global?.log(message: "STATE CHANGED: disconnected") - print("STATE CHANGED: disconnected") - case .disconnecting: - Logger.global?.log(message: "STATE CHANGED: disconnecting") - print("STATE CHANGED: disconnecting") - case .invalid: - Logger.global?.log(message: "STATE CHANGED: invalid") - print("STATE CHANGED: invalid") - case .reasserting: - Logger.global?.log(message: "STATE CHANGED: reasserting") - print("STATE CHANGED: reasserting") - default: - Logger.global?.log(message: "STATE CHANGED: unknown status") - print("STATE CHANGED: unknown status") - } - - // We care about "unknown" state changes. - if (session.status != .connected && session.status != .disconnected) { - return - } - - stateChangeCallback?(session.status == .connected) - } - - private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool { - guard - let proto = manager.protocolConfiguration, - let tunnelProto = proto as? NETunnelProviderProtocol - else { - Logger.global?.log(message: "Ignoring manager because the proto is invalid.") - return false - } - - if (tunnelProto.providerBundleIdentifier == nil) { - Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.") - return false - } - - if (tunnelProto.providerBundleIdentifier != vpnBundleID) { - Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.") - return false; - } - - Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)") - print("Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)") - return true - } - - @objc func connect(ssConfig: String, - ovpnConfig: String, - failureCallback: @escaping () -> Void) { - Logger.global?.log(message: "Logger Connecting") -// assert(tunnel != nil) - - self.openVPNConfig = ovpnConfig - self.shadowSocksConfig = ssConfig - - let addr: String = ovpnConfig - .splitToArray(separator: "\n", trimmingCharacters: nil) - .first { $0.starts(with: "remote ") } - .splitToArray(separator: " ", trimmingCharacters: nil)[1] - print("server: \(addr)") - - // Let's remove the previous config if it exists. - (tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() - - self.configureTunnel(withShadowSocks: self.shadowSocksConfig, serverAddress: addr, config: self.openVPNConfig, failureCallback: failureCallback) - } - - @objc func connect(ovpnConfig: String, failureCallback: @escaping () -> Void) { - Logger.global?.log(message: "Logger Connecting") -// assert(tunnel != nil) - - let addr: String = ovpnConfig - .splitToArray(separator: "\n", trimmingCharacters: nil) - .first { $0.starts(with: "remote ") } - .splitToArray(separator: " ", trimmingCharacters: nil)[1] - print("server: \(addr)") - - // Let's remove the previous config if it exists. - (tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() - - self.configureOpenVPNTunnel(serverAddress: addr, config: ovpnConfig, failureCallback: failureCallback) - } - - @objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, presharedKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) { - Logger.global?.log(message: "Logger Connecting") -// assert(tunnel != nil) - - // Let's remove the previous config if it exists. - (tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() - - let keyData = PublicKey(base64Key: serverPublicKey)! - let dnsServerIP = IPv4Address(dnsServer) - let ipv6GatewayIP = IPv6Address(serverIpv6Gateway) - - var peerConfiguration = PeerConfiguration(publicKey: keyData) - peerConfiguration.preSharedKey = PreSharedKey(base64Key: presharedKey) - peerConfiguration.endpoint = Endpoint(from: serverIpv4AddrIn + ":\(serverPort )") - peerConfiguration.allowedIPs = [] - - allowedIPAddressRanges.forEach { - if (!$0.isIpv6) { - peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv4Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) - } else if (ipv6Enabled) { - peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv6Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) - } - } - - var peerConfigurations: [PeerConfiguration] = [] - peerConfigurations.append(peerConfiguration) - - var interface = InterfaceConfiguration(privateKey: privateKey!) - - if let ipv4Address = IPAddressRange(from: deviceIpv4Address!), - let ipv6Address = IPAddressRange(from: deviceIpv6Address!) { - interface.addresses = [ipv4Address] - if (ipv6Enabled) { - interface.addresses.append(ipv6Address) - } - } - interface.dns = [ DNSServer(address: dnsServerIP!)] - interface.mtu = 1412 // 1280 - - if (ipv6Enabled) { - interface.dns.append(DNSServer(address: ipv6GatewayIP!)) - } - - let config = TunnelConfiguration(name: vpnName, interface: interface, peers: peerConfigurations) - - self.configureTunnel(config: config, reason: reason, failureCallback: failureCallback) - } - - func configureTunnel(config: TunnelConfiguration, reason: Int, failureCallback: @escaping () -> Void) { - guard let proto = NETunnelProviderProtocol(tunnelConfiguration: config) else { - failureCallback() - return - } - - guard tunnel != nil else { failureCallback(); return } - proto.providerBundleIdentifier = vpnBundleID - - tunnel!.protocolConfiguration = proto - tunnel!.localizedDescription = "Amnezia Wireguard" - tunnel!.isEnabled = true - - tunnel!.saveToPreferences { [unowned self] saveError in - if let error = saveError { - Logger.global?.log(message: "Connect Tunnel Save Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Saving the tunnel succeeded") - - self.tunnel!.loadFromPreferences { error in - if let error = error { - Logger.global?.log(message: "Connect Tunnel Load Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Loading the tunnel succeeded") - print("Loading the tunnel succeeded") - - do { - if (reason == 1 /* ReasonSwitching */) { - let settings = config.asWgQuickConfig() - let settingsData = settings.data(using: .utf8)! - try (self.tunnel!.connection as? NETunnelProviderSession)? - .sendProviderMessage(settingsData) { data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert response to string") - return - } - print("Config sent to NE: \(configString)") - } - } else { - print("starting tunnel") - try (self.tunnel!.connection as? NETunnelProviderSession)?.startTunnel() - } - } catch let error { - Logger.global?.log(message: "Something went wrong: \(error)") - failureCallback() - return - } - } - } - } - - func configureTunnel(withShadowSocks ssConfig: String?, serverAddress: String, config: String?, failureCallback: @escaping () -> Void) { - guard let ss = ssConfig, let ovpn = config else { failureCallback(); return } - let tunnelProtocol = NETunnelProviderProtocol() - tunnelProtocol.serverAddress = serverAddress - tunnelProtocol.providerBundleIdentifier = vpnBundleID - tunnelProtocol.providerConfiguration = ["ovpn": Data(ovpn.utf8), "ss": Data(ss.utf8)] - tunnel?.protocolConfiguration = tunnelProtocol - tunnel?.localizedDescription = "Amnezia ShadowSocks" - tunnel?.isEnabled = true - - tunnel?.saveToPreferences { [unowned self] saveError in - if let error = saveError { - Logger.global?.log(message: "Connect ShadowSocks Tunnel Save Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Saving ShadowSocks tunnel succeeded") - - self.tunnel?.loadFromPreferences { error in - if let error = error { - Logger.global?.log(message: "Connect ShadowSocks Tunnel Load Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Loading the ShadowSocks tunnel succeeded") - print("Loading the ss tunnel succeeded") - - do { - print("starting ss tunnel") - try self.tunnel?.connection.startVPNTunnel() - } catch let error { - Logger.global?.log(message: "Something went wrong: \(error)") - failureCallback() - return - } - } - } - } - - func configureOpenVPNTunnel(serverAddress: String, config: String, failureCallback: @escaping () -> Void) { - let tunnelProtocol = NETunnelProviderProtocol() - tunnelProtocol.serverAddress = serverAddress - tunnelProtocol.providerBundleIdentifier = vpnBundleID - tunnelProtocol.providerConfiguration = ["ovpn": Data(config.utf8)] - tunnel?.protocolConfiguration = tunnelProtocol - tunnel?.localizedDescription = "Amnezia OpenVPN" - tunnel?.isEnabled = true - - tunnel?.saveToPreferences { [unowned self] saveError in - if let error = saveError { - Logger.global?.log(message: "Connect OpenVPN Tunnel Save Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Saving the OpenVPN tunnel succeeded") - - self.tunnel?.loadFromPreferences { error in - if let error = error { - Logger.global?.log(message: "Connect OpenVPN Tunnel Load Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Loading the OpenVPN tunnel succeeded") - print("Loading the openvpn tunnel succeeded") - - do { - print("starting openvpn tunnel") - try self.tunnel?.connection.startVPNTunnel() - } catch let error { - Logger.global?.log(message: "Something went wrong: \(error)") - failureCallback() - return - } - } - } - } - - @objc func disconnect() { - Logger.global?.log(message: "Disconnecting") - guard tunnel != nil else { return } - (tunnel!.connection as? NETunnelProviderSession)?.stopTunnel() - } - - @objc func checkStatus(callback: @escaping (String, String, String) -> Void) { - let protoType = (tunnel!.localizedDescription ?? "").toTunnelType - - switch protoType { - case .wireguard: - checkWireguardStatus(callback: callback) - case .openvpn: - checkOVPNStatus(callback: callback) - case .shadowsocks: - checkShadowSocksStatus(callback: callback) - case .empty: - break - } - - } - - private func checkShadowSocksStatus(callback: @escaping (String, String, String) -> Void) { - guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else { - callback("", "", "") - return - } - - guard let ssData = proto.providerConfiguration?["ss"] as? Data, - let ssConfig = try? JSONSerialization.jsonObject(with: ssData, options: []) as? [String: Any], - let serverIpv4Gateway = ssConfig["remote_host"] as? String else { - callback("", "", "") - return - } - - let deviceIpv4Address = getWiFiAddress() - if deviceIpv4Address == nil { - callback("", "", "") - return - } - - guard let session = tunnel?.connection as? NETunnelProviderSession else { - callback("", "", "") - return - } - - do { - try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert data to string") - callback("", "", "") - return - } - - callback("\(serverIpv4Gateway)", "\(deviceIpv4Address!)", configString) - } - } catch { - Logger.global?.log(message: "Failed to retrieve data from session") - callback("", "", "") - } - } - - private func checkOVPNStatus(callback: @escaping (String, String, String) -> Void) { - guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else { - callback("", "", "") - return - } - - guard let configData = proto.providerConfiguration?["ovpn"] as? Data, - let ovpnConfig = String(data: configData, encoding: .utf8) else { - callback("", "", "") - return - } - - let serverIpv4Gateway: String = ovpnConfig - .splitToArray(separator: "\n", trimmingCharacters: nil) - .first { $0.starts(with: "remote ") } - .splitToArray(separator: " ", trimmingCharacters: nil)[1] - - let deviceIpv4Address = getWiFiAddress() - if deviceIpv4Address == nil { - callback("", "", "") - return - } - - guard let session = tunnel?.connection as? NETunnelProviderSession else { - callback("", "", "") - return - } - - do { - try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert data to string") - callback("", "", "") - return - } - - callback("\(serverIpv4Gateway)", "\(deviceIpv4Address!)", configString) - } - } catch { - Logger.global?.log(message: "Failed to retrieve data from session") - callback("", "", "") - } - } - - private func checkWireguardStatus(callback: @escaping (String, String, String) -> Void) { - Logger.global?.log(message: "Check Wireguard") - let proto = tunnel!.protocolConfiguration as? NETunnelProviderProtocol - if proto == nil { - callback("", "", "") - return - } - - let tunnelConfiguration = proto?.asTunnelConfiguration() - if tunnelConfiguration == nil { - callback("", "", "") - return - } - - let serverIpv4Gateway = tunnelConfiguration?.interface.dns[0].address - if serverIpv4Gateway == nil { - callback("", "", "") - return - } - - let deviceIpv4Address = tunnelConfiguration?.interface.addresses[0].address - if deviceIpv4Address == nil { - callback("", "", "") - return - } - - guard let session = tunnel?.connection as? NETunnelProviderSession - else { - callback("", "", "") - return - } - - do { - try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert data to string") - callback("", "", "") - return - } - - callback("\(serverIpv4Gateway!)", "\(deviceIpv4Address!)", configString) - } - } catch { - Logger.global?.log(message: "Failed to retrieve data from session") - callback("", "", "") - } - } - - func getWiFiAddress() -> String? { - var address : String? - - // Get list of all interfaces on the local machine: - var ifaddr : UnsafeMutablePointer? - guard getifaddrs(&ifaddr) == 0 else { return nil } - guard let firstAddr = ifaddr else { return nil } - - // For each interface ... - for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { - let interface = ifptr.pointee - - // Check for IPv4 or IPv6 interface: - let addrFamily = interface.ifa_addr.pointee.sa_family - //if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { // **ipv6 committed - if addrFamily == UInt8(AF_INET){ - - // Check interface name: - let name = String(cString: interface.ifa_name) - if name == "en0" { - - // Convert interface address to a human readable string: - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, socklen_t(hostname.count), - nil, socklen_t(0), NI_NUMERICHOST) - address = String(cString: hostname) - } - } - } - freeifaddrs(ifaddr) - - return address - } - - -} - - -enum TunnelType: String { - case wireguard, openvpn, shadowsocks, empty -} - -extension String { - var toTunnelType: TunnelType { - switch self { - case "Amnezia Wireguard": return .wireguard - case "Amnezia OpenVPN": return .openvpn - case "Amnezia ShadowSocks": return .shadowsocks - default: - return .empty - } - } -} diff --git a/client/platforms/ios/ipaddressrange.cpp b/client/platforms/ios/ipaddressrange.cpp deleted file mode 100644 index 8810a73c..00000000 --- a/client/platforms/ios/ipaddressrange.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "ipaddressrange.h" -#include "ipaddress.h" - -IPAddressRange::IPAddressRange(const QString& ipAddress, uint32_t range, - IPAddressType type) - : m_ipAddress(ipAddress), m_range(range), m_type(type) {} - -IPAddressRange::IPAddressRange(const QString& prefix) { - QStringList split = prefix.split('/'); - m_ipAddress = split[0]; - if (m_ipAddress.contains(':')) { - // Probably IPv6 - m_type = IPv6; - m_range = 128; - } else { - // Assume IPv4 - m_type = IPv4; - m_range = 32; - } - if (split.count() > 1) { - m_range = split[1].toUInt(); - } -} - -IPAddressRange::IPAddressRange(const IPAddressRange& other) { - *this = other; -} - -IPAddressRange& IPAddressRange::operator=(const IPAddressRange& other) { - if (this == &other) return *this; - - m_ipAddress = other.m_ipAddress; - m_range = other.m_range; - m_type = other.m_type; - - return *this; -} - -bool IPAddressRange::operator==(const IPAddressRange& other) const { - if (this == &other) return true; - - return m_ipAddress == other.m_ipAddress && m_range == other.m_range && - m_type == other.m_type; -} - -IPAddressRange::~IPAddressRange() { } - -// static -QList IPAddressRange::fromIPAddressList( - const QList& list) { - QList result; - for (const IPAddress& ip : list) { - result.append(IPAddressRange(ip.toString())); - } - return result; -} diff --git a/client/platforms/ios/ipaddressrange.h b/client/platforms/ios/ipaddressrange.h deleted file mode 100644 index db1733ae..00000000 --- a/client/platforms/ios/ipaddressrange.h +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef IPADDRESSRANGE_H -#define IPADDRESSRANGE_H - -#include -#include - -class IPAddress; - -class IPAddressRange final { - public: - enum IPAddressType { - IPv4, - IPv6, - }; - - static QList fromIPAddressList(const QList& list); - - IPAddressRange(const QString& prefix); - IPAddressRange(const QString& ipAddress, uint32_t range, IPAddressType type); - IPAddressRange(const IPAddressRange& other); - IPAddressRange& operator=(const IPAddressRange& other); - bool operator==(const IPAddressRange& other) const; - ~IPAddressRange(); - - const QString& ipAddress() const { return m_ipAddress; } - uint32_t range() const { return m_range; } - IPAddressType type() const { return m_type; } - const QString toString() const { - return QString("%1/%2").arg(m_ipAddress).arg(m_range); - } - - private: - QString m_ipAddress; - uint32_t m_range; - IPAddressType m_type; -}; - -#endif // IPADDRESSRANGE_H diff --git a/client/platforms/ios/ssadapterpacketflow.h b/client/platforms/ios/ssadapterpacketflow.h deleted file mode 100644 index 3d0f0423..00000000 --- a/client/platforms/ios/ssadapterpacketflow.h +++ /dev/null @@ -1,12 +0,0 @@ -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ShadowSocksAdapterPacketFlow - -- (void)readPacketsWithCompletionHandler:(void (^)(NSArray *packets, NSArray *protocols))completionHandler; -- (BOOL)writePackets:(NSArray *)packets withProtocols:(NSArray *)protocols; - -@end - -NS_ASSUME_NONNULL_END diff --git a/client/platforms/ios/ssconnectivity.h b/client/platforms/ios/ssconnectivity.h deleted file mode 100644 index bdc4234a..00000000 --- a/client/platforms/ios/ssconnectivity.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ShadowsocksConnectivity_h -#define ShadowsocksConnectivity_h - -#import -/** - * Non-thread-safe class to perform Shadowsocks connectivity checks. - */ -@interface ShadowsocksConnectivity : NSObject - -/** - * Initializes the object with a local Shadowsocks port, |shadowsocksPort|. - */ -- (id)initWithPort:(uint16_t)shadowsocksPort; - -/** - * Verifies that the server has enabled UDP forwarding. Performs an end-to-end test by sending - * a DNS request through the proxy. This method is a superset of |checkServerCredentials|, as its - * success implies that the server credentials are valid. - */ -- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion; - -/** - * Verifies that the server credentials are valid. Performs an end-to-end authentication test - * by issuing an HTTP HEAD request to a target domain through the proxy. - */ -- (void)checkServerCredentials:(void (^)(BOOL))completion; - -/** - * Checks that the server is reachable on |host| and |port|. - */ -- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion; - -@end - -#endif /* ShadowsocksConnectivity_h */ - diff --git a/client/platforms/ios/ssconnectivity.m b/client/platforms/ios/ssconnectivity.m deleted file mode 100644 index 69613320..00000000 --- a/client/platforms/ios/ssconnectivity.m +++ /dev/null @@ -1,334 +0,0 @@ -#import "./ssconnectivity.h" -#include -//#import - -@import CocoaAsyncSocket; -//@import CocoaLumberjack; - -static char *const kShadowsocksLocalAddress = "127.0.0.1"; - -static char *const kDnsResolverAddress = "208.67.222.222"; // OpenDNS -static const uint16_t kDnsResolverPort = 53; -static const size_t kDnsRequestNumBytes = 28; - -static const size_t kSocksHeaderNumBytes = 10; -static const uint8_t kSocksMethodsResponseNumBytes = 2; -static const size_t kSocksConnectResponseNumBytes = 10; -static const uint8_t kSocksVersion = 0x5; -static const uint8_t kSocksMethodNoAuth = 0x0; -static const uint8_t kSocksCmdConnect = 0x1; -static const uint8_t kSocksAtypIpv4 = 0x1; -static const uint8_t kSocksAtypDomainname = 0x3; - -static const NSTimeInterval kTcpSocketTimeoutSecs = 10.0; -static const NSTimeInterval kUdpSocketTimeoutSecs = 1.0; -static const long kSocketTagHttpRequest = 100; -static const int kUdpForwardingMaxChecks = 5; -static const uint16_t kHttpPort = 80; - -@interface ShadowsocksConnectivity () - -@property(nonatomic) uint16_t shadowsocksPort; - -@property(nonatomic, copy) void (^udpForwardingCompletion)(BOOL); -@property(nonatomic, copy) void (^reachabilityCompletion)(BOOL); -@property(nonatomic, copy) void (^credentialsCompletion)(BOOL); - -@property(nonatomic) dispatch_queue_t dispatchQueue; -@property(nonatomic) GCDAsyncUdpSocket *udpSocket; -@property(nonatomic) GCDAsyncSocket *credentialsSocket; -@property(nonatomic) GCDAsyncSocket *reachabilitySocket; - -@property(nonatomic) bool isRemoteUdpForwardingEnabled; -@property(nonatomic) bool areServerCredentialsValid; -@property(nonatomic) bool isServerReachable; -@property(nonatomic) int udpForwardingNumChecks; -@end - -@implementation ShadowsocksConnectivity - -- (id)initWithPort:(uint16_t)shadowsocksPort { - self = [super init]; - if (self) { - _shadowsocksPort = shadowsocksPort; - _dispatchQueue = dispatch_queue_create("ShadowsocksConnectivity", DISPATCH_QUEUE_SERIAL); - } - return self; -} - -#pragma mark - UDP Forwarding - -struct socks_udp_header { - uint16_t rsv; - uint8_t frag; - uint8_t atyp; - uint32_t addr; - uint16_t port; -}; - -- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion { -// DDLogInfo(@"Starting remote UDP forwarding check."); - self.isRemoteUdpForwardingEnabled = false; - self.udpForwardingNumChecks = 0; - self.udpForwardingCompletion = completion; - self.udpSocket = - [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; - struct in_addr dnsResolverAddress; - if (!inet_aton(kDnsResolverAddress, &dnsResolverAddress)) { -// DDLogError(@"Failed to convert DNS resolver IP."); - [self udpForwardingCheckDone:false]; - return; - } - struct socks_udp_header socksHeader = { - .atyp = kSocksAtypIpv4, - .addr = dnsResolverAddress.s_addr, // Already in network order - .port = htons(kDnsResolverPort)}; - uint8_t *dnsRequest = [self getDnsRequest]; - size_t packetNumBytes = kSocksHeaderNumBytes + kDnsRequestNumBytes; - uint8_t socksPacket[packetNumBytes]; - memset(socksPacket, 0, packetNumBytes); - memcpy(socksPacket, &socksHeader, kSocksHeaderNumBytes); - memcpy(socksPacket + kSocksHeaderNumBytes, dnsRequest, kDnsRequestNumBytes); - - NSData *packetData = [[NSData alloc] initWithBytes:socksPacket length:packetNumBytes]; - - dispatch_source_t timer = - dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue); - if (!timer) { -// DDLogError(@"Failed to create timer"); - [self udpForwardingCheckDone:false]; - return; - } - dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), - kUdpSocketTimeoutSecs * NSEC_PER_SEC, 0); - __weak ShadowsocksConnectivity *weakSelf = self; - dispatch_source_set_event_handler(timer, ^{ - if (++weakSelf.udpForwardingNumChecks > kUdpForwardingMaxChecks || - weakSelf.isRemoteUdpForwardingEnabled) { - dispatch_source_cancel(timer); - if (!weakSelf.isRemoteUdpForwardingEnabled) { - [weakSelf udpForwardingCheckDone:false]; - } - [weakSelf.udpSocket close]; - return; - } -// DDLogDebug(@"Checking remote server's UDP forwarding (%d of %d).", -// weakSelf.udpForwardingNumChecks, kUdpForwardingMaxChecks); - [weakSelf.udpSocket sendData:packetData - toHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress] - port:self.shadowsocksPort - withTimeout:kUdpSocketTimeoutSecs - tag:0]; - if (![weakSelf.udpSocket receiveOnce:nil]) { -// DDLogError(@"UDP socket failed to receive data"); - } - }); - dispatch_resume(timer); -} - -// Returns a byte representation of a DNS request for "google.com". -- (uint8_t *)getDnsRequest { - static uint8_t kDnsRequest[] = { - 0, 0, // [0-1] query ID - 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD). - 0, 1, // [4-5] QDCOUNT (number of queries) - 0, 0, // [6-7] ANCOUNT (number of answers) - 0, 0, // [8-9] NSCOUNT (number of name server records) - 0, 0, // [10-11] ARCOUNT (number of additional records) - 6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm', - 0, // null terminator of FQDN (root TLD) - 0, 1, // QTYPE, set to A - 0, 1 // QCLASS, set to 1 = IN (Internet) - }; - return kDnsRequest; -} - -#pragma mark - GCDAsyncUdpSocketDelegate - -- (void)udpSocket:(GCDAsyncUdpSocket *)sock - didNotSendDataWithTag:(long)tag - dueToError:(NSError *)error { -// DDLogError(@"Failed to send data on UDP socket"); -} - -- (void)udpSocket:(GCDAsyncUdpSocket *)sock - didReceiveData:(NSData *)data - fromAddress:(NSData *)address - withFilterContext:(id)filterContext { - if (!self.isRemoteUdpForwardingEnabled) { - // Only report success if it hasn't been done so already. - [self udpForwardingCheckDone:true]; - } -} - -- (void)udpForwardingCheckDone:(BOOL)enabled { -// DDLogInfo(@"Remote UDP forwarding %@", enabled ? @"enabled" : @"disabled"); - self.isRemoteUdpForwardingEnabled = enabled; - if (self.udpForwardingCompletion != NULL) { - self.udpForwardingCompletion(self.isRemoteUdpForwardingEnabled); - self.udpForwardingCompletion = NULL; - } -} - -#pragma mark - Credentials - -struct socks_methods_request { - uint8_t ver; - uint8_t nmethods; - uint8_t method; -}; - -struct socks_request_header { - uint8_t ver; - uint8_t cmd; - uint8_t rsv; - uint8_t atyp; -}; - -- (void)checkServerCredentials:(void (^)(BOOL))completion { -// DDLogInfo(@"Starting server creds. validation"); - self.areServerCredentialsValid = false; - self.credentialsCompletion = completion; - self.credentialsSocket = - [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; - NSError *error; - [self.credentialsSocket - connectToHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress] - onPort:self.shadowsocksPort - withTimeout:kTcpSocketTimeoutSecs - error:&error]; - if (error) { -// DDLogError(@"Unable to connect to local Shadowsocks server."); - [self serverCredentialsCheckDone]; - return; - } - - struct socks_methods_request methodsRequest = { - .ver = kSocksVersion, .nmethods = 0x1, .method = kSocksMethodNoAuth}; - NSData *methodsRequestData = - [[NSData alloc] initWithBytes:&methodsRequest length:sizeof(struct socks_methods_request)]; - [self.credentialsSocket writeData:methodsRequestData withTimeout:kTcpSocketTimeoutSecs tag:0]; - [self.credentialsSocket readDataToLength:kSocksMethodsResponseNumBytes - withTimeout:kTcpSocketTimeoutSecs - tag:0]; - - size_t socksRequestHeaderNumBytes = sizeof(struct socks_request_header); - NSString *domain = [self chooseRandomDomain]; - uint8_t domainNameNumBytes = domain.length; - size_t socksRequestNumBytes = socksRequestHeaderNumBytes + domainNameNumBytes + - sizeof(uint16_t) /* port */ + - sizeof(uint8_t) /* domain name length */; - - struct socks_request_header socksRequestHeader = { - .ver = kSocksVersion, .cmd = kSocksCmdConnect, .atyp = kSocksAtypDomainname}; - uint8_t socksRequest[socksRequestNumBytes]; - memset(socksRequest, 0x0, socksRequestNumBytes); - memcpy(socksRequest, &socksRequestHeader, socksRequestHeaderNumBytes); - socksRequest[socksRequestHeaderNumBytes] = domainNameNumBytes; - memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t), [domain UTF8String], - domainNameNumBytes); - uint16_t httpPort = htons(kHttpPort); - memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t) + domainNameNumBytes, - &httpPort, sizeof(uint16_t)); - - NSData *socksRequestData = - [[NSData alloc] initWithBytes:socksRequest length:socksRequestNumBytes]; - [self.credentialsSocket writeData:socksRequestData withTimeout:kTcpSocketTimeoutSecs tag:0]; - [self.credentialsSocket readDataToLength:kSocksConnectResponseNumBytes - withTimeout:kTcpSocketTimeoutSecs - tag:0]; - - NSString *httpRequest = - [[NSString alloc] initWithFormat:@"HEAD / HTTP/1.1\r\nHost: %@\r\n\r\n", domain]; - [self.credentialsSocket - writeData:[NSData dataWithBytes:[httpRequest UTF8String] length:httpRequest.length] - withTimeout:kTcpSocketTimeoutSecs - tag:kSocketTagHttpRequest]; - [self.credentialsSocket readDataWithTimeout:kTcpSocketTimeoutSecs tag:kSocketTagHttpRequest]; - [self.credentialsSocket disconnectAfterReading]; -} - -// Returns a statically defined array containing domain names for validating server credentials. -+ (const NSArray *)getCredentialsValidationDomains { - static const NSArray *kCredentialsValidationDomains; - static dispatch_once_t kDispatchOnceToken; - dispatch_once(&kDispatchOnceToken, ^{ - // We have chosen these domains due to their neutrality. - kCredentialsValidationDomains = - @[ @"eff.org", @"ietf.org", @"w3.org", @"wikipedia.org", @"example.com" ]; - }); - return kCredentialsValidationDomains; -} - -// Returns a random domain from |kCredentialsValidationDomains|. -- (NSString *)chooseRandomDomain { - const NSArray *domains = [ShadowsocksConnectivity getCredentialsValidationDomains]; - int index = arc4random_uniform((uint32_t)domains.count); - return domains[index]; -} - -// Calls |credentialsCompletion| once with |areServerCredentialsValid|. -- (void)serverCredentialsCheckDone { -// DDLogInfo(@"Server creds. %@.", self.areServerCredentialsValid ? @"succeeded" : @"failed"); - if (self.credentialsCompletion != NULL) { - self.credentialsCompletion(self.areServerCredentialsValid); - self.credentialsCompletion = NULL; - } -} - -#pragma mark - Reachability - -- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion { -// DDLogInfo(@"Starting server reachability check."); - self.isServerReachable = false; - self.reachabilityCompletion = completion; - self.reachabilitySocket = - [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; - NSError *error; - [self.reachabilitySocket connectToHost:host - onPort:port - withTimeout:kTcpSocketTimeoutSecs - error:&error]; - if (error) { -// DDLogError(@"Unable to connect to Shadowsocks server."); - return; - } -} - -// Calls |reachabilityCompletion| once with |isServerReachable|. -- (void)reachabilityCheckDone { -// DDLogInfo(@"Server %@.", self.isServerReachable ? @"reachable" : @"unreachable"); - if (self.reachabilityCompletion != NULL) { - self.reachabilityCompletion(self.isServerReachable); - self.reachabilityCompletion = NULL; - } -} - -#pragma mark - GCDAsyncSocketDelegate - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { - // We don't need to inspect any of the data, as the SOCKS responses are hardcoded in ss-local and - // the fact that we have read the HTTP response indicates that the server credentials are valid. - if (tag == kSocketTagHttpRequest && data != nil) { - NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - self.areServerCredentialsValid = httpResponse != nil && [httpResponse hasPrefix:@"HTTP/1.1"]; - } -} - -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { - if ([self.reachabilitySocket isEqual:sock]) { - self.isServerReachable = true; - [self.reachabilitySocket disconnect]; - } -} - -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error { - if ([self.reachabilitySocket isEqual:sock]) { - [self reachabilityCheckDone]; - } else { - [self serverCredentialsCheckDone]; - } -} - -@end - diff --git a/client/platforms/ios/sspacket.h b/client/platforms/ios/sspacket.h deleted file mode 100644 index cdd6e1fa..00000000 --- a/client/platforms/ios/sspacket.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef sspacket_h -#define sspacket_h - -#import - -@interface SSPacket : NSObject -@property (readonly, nonatomic) NSData *vpnData; -@property (readonly, nonatomic) NSData *ssPacketFlowData; -@property (readonly,nonatomic) NSNumber *protocolFamily; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithSSData:(NSData *)data NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithPacketFlowData:(NSData *)data protocolFamily:(NSNumber *)protocolFamily NS_DESIGNATED_INITIALIZER; -@end - -#endif /* sspacket_h */ diff --git a/client/platforms/ios/sspacket.m b/client/platforms/ios/sspacket.m deleted file mode 100644 index 1326a686..00000000 --- a/client/platforms/ios/sspacket.m +++ /dev/null @@ -1,57 +0,0 @@ -#import - -#include - -@interface SSPacket () { - NSData *_data; - NSNumber *_protocolFamily; -} -@end - -@implementation SSPacket - -- (instancetype)initWithSSData:(NSData *)data { - if (self = [super init]) { -// NSUInteger prefix_size = sizeof(uint32_t); -// uint32_t protocol = PF_UNSPEC; -// [data getBytes:&protocol length:prefix_size]; -// protocol = CFSwapInt32HostToBig(protocol); -// -// NSRange range = NSMakeRange(prefix_size, data.length - prefix_size); -// NSData *packetData = [data subdataWithRange:range]; - -// _data = packetData; -// _protocolFamily = @(protocol); - _data = data; - _protocolFamily = @(PF_INET); - } - return self; -} - -- (instancetype)initWithPacketFlowData:(NSData *)data protocolFamily:(NSNumber *)protocolFamily { - if (self = [super init]) { - _data = data; - _protocolFamily = protocolFamily; - } - return self; -} - -- (NSData *)vpnData { -// uint32_t prefix = CFSwapInt32HostToBig(_protocolFamily.unsignedIntegerValue); -// NSUInteger prefix_size = sizeof(uint32_t); -// NSMutableData *data = [NSMutableData dataWithCapacity:prefix_size + _data.length]; -// -// [data appendBytes:&prefix length:prefix_size]; -// [data appendData:_data]; - return _data; -} - -- (NSData *)ssPacketFlowData { - return _data; -} - -- (NSNumber *)protocolFamily { - return _protocolFamily; -} - -@end diff --git a/client/platforms/ios/ssprovider.swift b/client/platforms/ios/ssprovider.swift deleted file mode 100644 index 5d503a0a..00000000 --- a/client/platforms/ios/ssprovider.swift +++ /dev/null @@ -1,293 +0,0 @@ -import Foundation -import ShadowSocks - -import NetworkExtension -import Darwin - -enum ErrorCode: Int { - case noError = 0 - case undefinedError - case vpnPermissionNotGranted - case invalidServerCredentials - case udpRelayNotEnabled - case serverUnreachable - case vpnStartFailure - case illegalServerConfiguration - case shadowsocksStartFailure - case configureSystemProxyFailure - case noAdminPermissions - case unsupportedRoutingTable - case systemMisconfigured -} - -protocol ShadowSocksTunnel { - var ssLocalThreadId: pthread_t? { get } - func start(usingPacketFlow packetFlow: ShadowSocksAdapterPacketFlow, - withConnectivityCheck connectivityCheck: Bool, - completion: @escaping (ErrorCode?) -> Void) - func stop(completion: @escaping (ErrorCode?) -> Void) - func isUp() -> Bool -} - -final class SSProvider: NSObject, ShadowSocksTunnel { - private var startCompletion: ((ErrorCode?) -> Void)? = nil - private var stopCompletion: ((ErrorCode?) -> Void)? = nil - - private var dispatchQueue: DispatchQueue - private var processQueue: DispatchQueue - private var dispatchGroup: DispatchGroup - - private var connectivityCheck: Bool = false - private var ssConnectivity: ShadowsocksConnectivity? = nil - - private var config: Data - - private var ssPacketFlowBridge: ShadowSocksAdapterFlowBridge? = nil - - var ssLocalThreadId: pthread_t? = nil - - init(config: Data, localPort: Int = 8585) { - self.config = config - self.dispatchQueue = DispatchQueue(label: "org.amnezia.shadowsocks") - self.processQueue = DispatchQueue(label: "org.amnezia.packet-processor") - self.dispatchGroup = DispatchGroup() - self.ssPacketFlowBridge = .init() - self.ssConnectivity = ShadowsocksConnectivity.init(port: NSNumber(value: localPort).uint16Value) - super.init() - } - - func isUp() -> Bool { - return ssLocalThreadId != nil - } - - func start(usingPacketFlow packetFlow: ShadowSocksAdapterPacketFlow, withConnectivityCheck connectivityCheck: Bool = false, completion: @escaping (ErrorCode?) -> Void) { - guard ssLocalThreadId == nil else { - wg_log(.error, message: "SS detached thread has already been started with id \(ssLocalThreadId!)") - completion(.shadowsocksStartFailure) - return - } - - wg_log(.info, message: "Starting SSProvider...") - self.connectivityCheck = connectivityCheck - wg_log(.info, message: "ssPacketFlowBridge is \(ssPacketFlowBridge != nil ? "not null" : "null")") - self.ssPacketFlowBridge?.ssPacketFlow = packetFlow - wg_log(.info, message: "ssPacketFlow is \(ssPacketFlowBridge?.ssPacketFlow != nil ? "not null" : "null")") - self.startCompletion = completion - dispatchQueue.async { - wg_log(.info, message: "Starting ss thread...") - self.startShadowSocksThread() - } - } - - func stop(completion: @escaping (ErrorCode?) -> Void) { - guard ssLocalThreadId != nil else { return } - self.stopCompletion = completion - dispatchQueue.async { - pthread_kill(self.ssLocalThreadId!, SIGUSR1) - self.ssPacketFlowBridge?.invalidateSocketsIfNeeded() - self.ssPacketFlowBridge = nil - self.ssLocalThreadId = nil - } - } - - private func onShadowsocksCallback(socks_fd: Int32, udp_fd: Int32, obs: UnsafeMutableRawPointer) { - NSLog("Inside onShadowsocksCallback() with port \(socks_fd)") - wg_log(.debug, message: "Inside onShadowsocksCallback() with port \(socks_fd)") - var error: NSError? = nil - if (socks_fd <= 0 && udp_fd <= 0) { - error = NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", code: 100, userInfo: [NSLocalizedDescriptionKey : "Failed to start shadowsocks proxy"]) - NSLog("onShadowsocksCallback with error \(error!.localizedDescription)") - wg_log(.debug, message: "onShadowsocksCallback failed with error \(error!.localizedDescription)") -// return - } - let ss = Unmanaged.fromOpaque(obs).takeUnretainedValue() - ss.establishTunnel() - ss.checkServerConnectivity() - } - - private func startShadowSocksThread() { - var attr: pthread_attr_t = .init() - var err = pthread_attr_init(&attr) - wg_log(.debug, message: "pthread_attr_init returned \(err)") - if (err != 0) { - NSLog("pthread_attr_init failed with error \(err)") - wg_log(.debug, message: "pthread_attr_init failed with error \(err)") - startCompletion?(.shadowsocksStartFailure) - return - } - err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) - wg_log(.debug, message: "pthread_attr_setdetachstate returned \(err)") - if (err != 0) { - wg_log(.debug, message: "pthread_attr_setdetachstate failed with error \(err)") - startCompletion?(.shadowsocksStartFailure) - return - } - let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - err = pthread_create(&ssLocalThreadId, &attr, { obs in - let ss = Unmanaged.fromOpaque(obs).takeUnretainedValue() - ss.startShadowSocks() - return nil - }, observer) - wg_log(.debug, message: "pthread_create returned \(err)") - if (err != 0) { - NSLog("pthread_create failed with error \(err)") - wg_log(.debug, message: "pthread_create failed with error \(err)") - startCompletion?(.shadowsocksStartFailure) - return - } - err = pthread_attr_destroy(&attr) - wg_log(.debug, message: "pthread_attr_destroy returned \(err)") - if (err != 0) { - NSLog("pthread_create failed with error \(err)") - wg_log(.debug, message: "pthread_attr_destroy failed with error \(err)") - startCompletion?(.shadowsocksStartFailure) - return - } - } - - private func startShadowSocks() { - wg_log(.debug, message: "startShadowSocks with config \(config)") - let str = String(decoding: config, as: UTF8.self) - wg_log(.info, message: "startShadowSocks -> config: \(str)") - guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else { - startCompletion?(.configureSystemProxyFailure) - return - } - - wg_log(.info, message: "SS Config: \(ssConfig)") - - guard let remoteHost = ssConfig["server"] as? String, // UnsafeMutablePointer, - let remotePort = ssConfig["server_port"] as? Int32, - let localAddress = ssConfig["local_addr"] as? String, //UnsafeMutablePointer, - let localPort = ssConfig["local_port"] as? Int32, - let method = ssConfig["method"] as? String, //UnsafeMutablePointer, - let password = ssConfig["password"] as? String,//UnsafeMutablePointer, - let timeout = ssConfig["timeout"] as? Int32 - else { - startCompletion?(.configureSystemProxyFailure) - return - } - - /* An example profile - * - * const profile_t EXAMPLE_PROFILE = { - * .remote_host = "example.com", - * .local_addr = "127.0.0.1", - * .method = "bf-cfb", - * .password = "barfoo!", - * .remote_port = 8338, - * .local_port = 1080, - * .timeout = 600; - * .acl = NULL, - * .log = NULL, - * .fast_open = 0, - * .mode = 0, - * .verbose = 0 - * }; - */ - - var profile: profile_t = .init() - memset(&profile, 0, MemoryLayout.size) - profile.remote_host = strdup(remoteHost) - profile.remote_port = remotePort - profile.local_addr = strdup(localAddress) - profile.local_port = localPort - profile.method = strdup(method) - profile.password = strdup(password) - profile.timeout = timeout - profile.acl = nil - profile.log = nil -// profile.mtu = 1600 - profile.fast_open = 1 - profile.mode = 0 - profile.verbose = 1 - - NSLog("Prepare to start shadowsocks proxy server...") - wg_log(.debug, message: "Prepare to start shadowsocks proxy server...") - let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - let success = start_ss_local_server_with_callback(profile, { socks_fd, udp_fd, data in - wg_log(.debug, message: "Inside cb callback") - wg_log(.debug, message: "Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)") - NSLog("Inside cb callback: Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)") - if let obs = data { - NSLog("Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)") - wg_log(.debug, message: "Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)") - let mySelf = Unmanaged.fromOpaque(obs).takeUnretainedValue() - mySelf.onShadowsocksCallback(socks_fd: socks_fd, udp_fd: udp_fd, obs: obs) - } - }, observer) - if success < 0 { - NSLog("Failed to start ss proxy") - wg_log(.error, message: "Failed to start ss proxy") - startCompletion?(.shadowsocksStartFailure) - return - } else { - NSLog("ss proxy started on port \(localPort)") - wg_log(.error, message: "ss proxy started on port \(localPort)") - stopCompletion?(.noError) - stopCompletion = nil - } - } - - private func establishTunnel() { - wg_log(.error, message: "Establishing tunnel") - do { - try ssPacketFlowBridge?.configureSocket() - processQueue.async { - wg_log(.error, message: "Start processing packets") - self.ssPacketFlowBridge?.processPackets() - } - } catch (let err) { - wg_log(.error, message: "ss failed creating sockets \(err.localizedDescription)") - ssPacketFlowBridge?.invalidateSocketsIfNeeded() - } - } - - private func checkServerConnectivity() { - guard connectivityCheck else { - startCompletion?(.noError) - return - } - - let str = String(decoding: config, as: UTF8.self) - wg_log(.info, message: "checkServerConnectivity -> config: \(str)") - guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any], - let remoteHost = ssConfig["server"] as? String, - let remotePort = ssConfig["server_port"] as? Int32 else { - startCompletion?(.configureSystemProxyFailure) - return - } - - var isRemoteUdpForwardingEnabled = false - var serverCredentialsAreValid = false - var isServerReachable = false - - dispatchGroup.enter() - ssConnectivity?.isUdpForwardingEnabled { status in - isRemoteUdpForwardingEnabled = status - self.dispatchGroup.leave() - } - dispatchGroup.enter() - ssConnectivity?.isReachable(remoteHost, port: NSNumber(value: remotePort).uint16Value) { status in - isServerReachable = status - self.dispatchGroup.leave() - } - dispatchGroup.enter() - ssConnectivity?.checkServerCredentials{ status in - serverCredentialsAreValid = status - self.dispatchGroup.leave() - } - - dispatchGroup.notify(queue: dispatchQueue) { - if isRemoteUdpForwardingEnabled { - self.startCompletion?(.noError) - } else if serverCredentialsAreValid { - self.startCompletion?(.udpRelayNotEnabled) - } else if isServerReachable { - self.startCompletion?(.invalidServerCredentials) - } else { - self.startCompletion?(.serverUnreachable) - } - } - } -} diff --git a/client/platforms/ios/tun2sockswriter.swift b/client/platforms/ios/tun2sockswriter.swift deleted file mode 100644 index 43793865..00000000 --- a/client/platforms/ios/tun2sockswriter.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation -import NetworkExtension -import Tun2socks - -class AmneziaTun2SocksWriter: NSObject, Tun2socksTunWriterProtocol { - var tunnelFlow: NEPacketTunnelFlow - - init( withPacketFlow nepflow: NEPacketTunnelFlow) { - self.tunnelFlow = nepflow - super.init() - } - - func write(_ p0: Data?, n: UnsafeMutablePointer?) throws { - if let packets = p0 { - tunnelFlow.writePackets([packets], withProtocols: [NSNumber(value: PF_INET)]) - } - } - - func close() throws {} -} - diff --git a/client/platforms/ios/tun2ssprovider.swift b/client/platforms/ios/tun2ssprovider.swift deleted file mode 100644 index 58c509f4..00000000 --- a/client/platforms/ios/tun2ssprovider.swift +++ /dev/null @@ -1,192 +0,0 @@ -import Foundation -import Darwin - -enum TunnelError: Error, Equatable { - case noError - case notFound(String) - case undefinedError(String) - case invalidServerCredentials(String) - case serverUnreachable(String) - case tunnelStartFailure(String) - case illegalServerConfiguration(String) - case systemMisconfigured(String) - - var desc: String { - switch self { - case .noError: return "OK. No errors." - case .notFound(let msg): return msg - case .undefinedError(let msg): return msg - case .invalidServerCredentials(let msg): return msg - case .serverUnreachable(let msg): return msg - case .tunnelStartFailure(let msg): return msg - case .illegalServerConfiguration(let msg): return msg - case .systemMisconfigured(let msg): return msg - } - } -} - -enum LeafProviderError: Int32 { - case ok = 0 - case invalidConfigPath - case invalidConfig - case ioError - case configFileWatcherError - case asyncChannelSendError - case asyncChannelRecvError - case runtimeManagerError - case noConfigFound - - static func toValue(from errorCode: Int32) -> LeafProviderError { - switch errorCode { - case ERR_OK: return ok - case ERR_CONFIG_PATH: return invalidConfigPath - case ERR_CONFIG: return invalidConfig - case ERR_IO: return ioError - case ERR_WATCHER: return configFileWatcherError - case ERR_ASYNC_CHANNEL_SEND: return asyncChannelSendError - case ERR_SYNC_CHANNEL_RECV: return asyncChannelRecvError - case ERR_RUNTIME_MANAGER: return runtimeManagerError - case ERR_NO_CONFIG_FILE: return noConfigFound - default: return ok - } - } - - var desc: String { - switch self { - case .ok: return "Ok. No Errors." - case .invalidConfigPath: return "Config file path is invalid." - case .invalidConfig: return "Config parsing error." - case .ioError: return "IO error." - case .configFileWatcherError: return "Config file watcher error." - case .asyncChannelSendError: return "Async channel send error." - case .asyncChannelRecvError: return "Sync channel receive error." - case .runtimeManagerError: return "Runtime manager error." - case .noConfigFound: return "No associated config file." - } - } -} - -class TunProvider: NSObject { - private var configPath: UnsafePointer - private var tunId: UInt16 - private var tunThreadId: pthread_t? = nil - private var dispatchQueue: DispatchQueue - - private var startCompletion: ((TunnelError?) -> Void)? = nil - private var stopCompletion: ((TunnelError?) -> Void)? = nil - - init(withConfig configPath: UnsafePointer) { - self.configPath = configPath - self.tunId = NSNumber(value: arc4random_uniform(UInt32.max)).uint16Value - self.dispatchQueue = DispatchQueue(label: "org.amnezia.ss-tun-openvpn") - super.init() - } - - func startTunnel(completion: @escaping (TunnelError?) -> Void) { - self.startCompletion = completion - guard tunThreadId == nil else { - let errMsg = "Leaf tunnel detached thread has already been started with id \(tunThreadId!)" - wg_log(.error, message: errMsg) - startCompletion?(.tunnelStartFailure(errMsg)) - return - } - wg_log(.info, message: "Starting tunnel thread...") - dispatchQueue.async { - self.startTunnelThread() - } - startCompletion?(.noError) - } - - private func startLeafTunnel() { - let err = leaf_run(tunId, configPath) - if (err != ERR_OK) { - let errMsg = LeafProviderError.toValue(from: err).desc - startCompletion?(.tunnelStartFailure(errMsg)) - } - } - - private func startTunnelThread() { - var attr: pthread_attr_t = .init() - var err = pthread_attr_init(&attr) - wg_log(.debug, message: "pthread_attr_init returned \(err)") - if (err != 0) { - let errMsg = "pthread_attr_init failed with error \(err)" - wg_log(.debug, message: errMsg) - startCompletion?(.tunnelStartFailure(errMsg)) - return - } - err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) - wg_log(.debug, message: "pthread_attr_setdetachstate returned \(err)") - if (err != 0) { - let errMsg = "pthread_attr_setdetachstate failed with error \(err)" - wg_log(.debug, message: errMsg) - startCompletion?(.tunnelStartFailure(errMsg)) - return - } - let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - err = pthread_create(&tunThreadId, &attr, { obs in - let provider = Unmanaged.fromOpaque(obs).takeUnretainedValue() - provider.startLeafTunnel() - return nil - }, observer) - wg_log(.debug, message: "pthread_create returned \(err)") - if (err != 0) { - let errMsg = "pthread_create failed with error \(err)" - wg_log(.debug, message: errMsg) - startCompletion?(.tunnelStartFailure(errMsg)) - return - } - err = pthread_attr_destroy(&attr) - wg_log(.debug, message: "pthread_attr_destroy returned \(err)") - if (err != 0) { - let errMsg = "pthread_create failed with error \(err)" - wg_log(.debug, message: errMsg) - startCompletion?(.tunnelStartFailure(errMsg)) - return - } - } - - func reloadTunnel(withId tunId: UInt16) { - let err = leaf_reload(tunId) - if (err != ERR_OK) { - let errMsg = LeafProviderError.toValue(from: err).desc - self.startCompletion?(.systemMisconfigured(errMsg)) - } - } - - func stopTunnel(completion: @escaping (TunnelError?) -> Void) { - self.stopCompletion = completion - guard tunThreadId != nil else { - let errMsg = "Leaf tunnel is not initialized properly or not started/existed, tunnelId is missed" - wg_log(.error, message: errMsg) - stopCompletion?(.notFound(errMsg)) - return - } - - dispatchQueue.async { - let success = leaf_shutdown(self.tunId) - if !success { - let errMsg = "Tunnel cannot be stopped for some odd reason." - self.stopCompletion?(.undefinedError(errMsg)) - } - pthread_kill(self.tunThreadId!, SIGUSR1) - self.stopCompletion?(.noError) - self.tunThreadId = nil - self.stopCompletion = nil - } - } - - func testConfig(onPath path: UnsafePointer, completion: @escaping (TunnelError?) -> Void) { - self.startCompletion = completion - let err = leaf_test_config(configPath) - if (err != ERR_OK) { - let errMsg = LeafProviderError.toValue(from: err).desc - startCompletion?(.illegalServerConfiguration(errMsg)) - return - } - startCompletion?(.noError) - } - - - -} diff --git a/client/protocols/ios_vpnprotocol.h b/client/protocols/ios_vpnprotocol.h deleted file mode 100644 index 9b4057e5..00000000 --- a/client/protocols/ios_vpnprotocol.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef IOS_VPNPROTOCOL_H -#define IOS_VPNPROTOCOL_H - -#include "vpnprotocol.h" -#include "protocols/protocols_defs.h" - -using namespace amnezia; - - -class IOSVpnProtocol : public VpnProtocol -{ - Q_OBJECT - -public: - explicit IOSVpnProtocol(amnezia::Proto proto, const QJsonObject& configuration, QObject* parent = nullptr); - static IOSVpnProtocol* instance(); - - virtual ~IOSVpnProtocol() override; - - bool initialize(); - - virtual ErrorCode start() override; - virtual void stop() override; - - void resume_start(); - - void checkStatus(); - - void setNotificationText(const QString& title, const QString& message, - int timerSec); - void setFallbackConnectedNotification(); - - void getBackendLogs(std::function&& callback); - - void cleanupBackendLogs(); - -signals: - void newTransmittedDataCount(quint64 rxBytes, quint64 txBytes); - -protected slots: - -protected: - -private: - Proto m_protocol; - bool m_serviceConnected = false; - bool m_checkingStatus = false; - std::function m_logCallback; - - bool m_isChangingState = false; - - void setupWireguardProtocol(const QJsonObject& rawConfig); - void setupOpenVPNProtocol(const QJsonObject& rawConfig); - void setupCloakProtocol(const QJsonObject& rawConfig); - void setupShadowSocksProtocol(const QJsonObject& rawConfig); - - void launchWireguardTunnel(const QJsonObject& rawConfig); - void launchOpenVPNTunnel(const QJsonObject& rawConfig); - void launchCloakTunnel(const QJsonObject& rawConfig); - void launchShadowSocksTunnel(const QJsonObject& rawConfig); - - QString serializeSSConfig(const QJsonObject &ssConfig); -}; - - -#endif // IOS_VPNPROTOCOL_H diff --git a/client/protocols/ios_vpnprotocol.mm b/client/protocols/ios_vpnprotocol.mm deleted file mode 100644 index cc38cf53..00000000 --- a/client/protocols/ios_vpnprotocol.mm +++ /dev/null @@ -1,662 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "platforms/ios/ipaddressrange.h" -#include "ios_vpnprotocol.h" -#include "core/errorstrings.h" -#include "AmneziaVPN-Swift.h" -#include "UIKit/UIKit.h" - - -namespace -{ -IOSVpnProtocol* s_instance = nullptr; -IOSVpnProtocolImpl* m_controller = nullptr; -Proto currentProto = amnezia::Proto::Any; -} - -IOSVpnProtocol::IOSVpnProtocol(Proto proto, const QJsonObject &configuration, QObject* parent) -: VpnProtocol(configuration, parent), m_protocol(proto) -{ - connect(this, &IOSVpnProtocol::newTransmittedDataCount, this, &IOSVpnProtocol::setBytesChanged); -} - -IOSVpnProtocol::~IOSVpnProtocol() -{ - qDebug() << "IOSVpnProtocol::~IOSVpnProtocol()"; - IOSVpnProtocol::stop(); -} - -IOSVpnProtocol* IOSVpnProtocol::instance() { - return s_instance; -} - -bool IOSVpnProtocol::initialize() -{ - if (!m_controller) { - QString protoName = m_rawConfig["protocol"].toString(); - - if (protoName == "wireguard") { - setupWireguardProtocol(m_rawConfig); - currentProto = amnezia::Proto::WireGuard; - } else if (protoName == "openvpn") { - setupOpenVPNProtocol(m_rawConfig); - currentProto = amnezia::Proto::OpenVpn; - } else if (protoName == "shadowsocks") { - setupShadowSocksProtocol(m_rawConfig); - currentProto = amnezia::Proto::ShadowSocks; - } else if (protoName == "cloak") { - setupCloakProtocol(m_rawConfig); - currentProto = amnezia::Proto::Cloak; - } else { - return false; - } - } - return true; -} - - -ErrorCode IOSVpnProtocol::start() -{ - if (m_isChangingState) - return NoError; - - if (!m_controller) - initialize(); - - switch (m_protocol) { - case amnezia::Proto::Cloak: - if (currentProto != m_protocol) { - if (m_controller) { - stop(); - initialize(); - } - launchCloakTunnel(m_rawConfig); - currentProto = amnezia::Proto::OpenVpn; - return NoError; - } - initialize(); - launchCloakTunnel(m_rawConfig); - break; - case amnezia::Proto::OpenVpn: - if (currentProto != m_protocol) { - if (m_controller) { - stop(); - initialize(); - } - launchOpenVPNTunnel(m_rawConfig); - currentProto = amnezia::Proto::OpenVpn; - return NoError; - } - initialize(); - launchOpenVPNTunnel(m_rawConfig); - break; - case amnezia::Proto::WireGuard: - if (currentProto != m_protocol) { - if (m_controller) { - stop(); - initialize(); - } - launchWireguardTunnel(m_rawConfig); - currentProto = amnezia::Proto::WireGuard; - return NoError; - } - initialize(); - launchWireguardTunnel(m_rawConfig); - break; - case amnezia::Proto::ShadowSocks: - if (currentProto != m_protocol) { - if (m_controller) { - stop(); - initialize(); - } - launchShadowSocksTunnel(m_rawConfig); - currentProto = amnezia::Proto::ShadowSocks; - return NoError; - } - initialize(); - launchShadowSocksTunnel(m_rawConfig); - break; - default: - break; - } - - return NoError; -} - -void IOSVpnProtocol::stop() -{ - if (!m_controller) { - qDebug() << "Not correctly initialized"; - return; - } - - [m_controller disconnect]; - - emit connectionStateChanged(Disconnected); - - [m_controller dealloc]; - m_controller = nullptr; -} - -void IOSVpnProtocol::resume_start() -{ - -} - -void IOSVpnProtocol::checkStatus() -{ - if (m_checkingStatus) { - return; - } - - if (!m_controller) { - return; - } - - m_checkingStatus = true; - - QPointer weakSelf = this; - - [m_controller checkStatusWithCallback:^(NSString* serverIpv4Gateway, NSString* deviceIpv4Address, - NSString* configString) { - if (!weakSelf) return; - QString config = QString::fromNSString(configString); - - m_checkingStatus = false; - - if (config.isEmpty()) { - return; - } - - uint64_t txBytes = 0; - uint64_t rxBytes = 0; - - QStringList lines = config.split("\n"); - for (const QString& line : lines) { - if (line.startsWith("tx_bytes=")) { - txBytes = line.split("=")[1].toULongLong(); - } else if (line.startsWith("rx_bytes=")) { - rxBytes = line.split("=")[1].toULongLong(); - } - - if (txBytes && rxBytes) { - break; - } - } - - emit weakSelf->newTransmittedDataCount(rxBytes, txBytes); - }]; -} - -void IOSVpnProtocol::setNotificationText(const QString &title, const QString &message, int timerSec) -{ - // TODO: add user notifications? -} - -void IOSVpnProtocol::setFallbackConnectedNotification() -{ - // TODO: add default user notifications? -} - -void IOSVpnProtocol::getBackendLogs(std::function &&callback) -{ - std::function a_callback = std::move(callback); - - QString groupId(GROUP_ID); - NSURL* groupPath = [[NSFileManager defaultManager] - containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; - - NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; - - QFile file(QString::fromNSString([path path])); - if (!file.open(QIODevice::ReadOnly)) { - a_callback("Network extension log file missing or unreadable."); - return; - } - - QByteArray content = file.readAll(); - a_callback(content); -} - -void IOSVpnProtocol::cleanupBackendLogs() -{ - QString groupId(GROUP_ID); - NSURL* groupPath = [[NSFileManager defaultManager] - containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; - - NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; - - QFile file(QString::fromNSString([path path])); - file.remove(); -} - -void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) -{ - static bool creating = false; - // No nested creation! - Q_ASSERT(creating == false); - creating = true; - - QJsonObject config = rawConfig["wireguard_config_data"].toObject(); - - QString privateKey = config["client_priv_key"].toString(); - QByteArray key = QByteArray::fromBase64(privateKey.toLocal8Bit()); - - QString addr = config["config"].toString().split("\n").takeAt(1).split(" = ").takeLast(); - QString dns = config["config"].toString().split("\n").takeAt(2).split(" = ").takeLast(); - QString privkey = config["config"].toString().split("\n").takeAt(3).split(" = ").takeLast(); - QString pubkey = config["config"].toString().split("\n").takeAt(6).split(" = ").takeLast(); - QString presharedkey = config["config"].toString().split("\n").takeAt(7).split(" = ").takeLast(); - QString allowedips = config["config"].toString().split("\n").takeAt(8).split(" = ").takeLast(); - QString endpoint = config["config"].toString().split("\n").takeAt(9).split(" = ").takeLast(); - QString keepalive = config["config"].toString().split("\n").takeAt(10).split(" = ").takeLast(); - - m_controller = [[IOSVpnProtocolImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID - privateKey:key.toNSData() - deviceIpv4Address:addr.toNSString() - deviceIpv6Address:@"::/0" - closure:^(ConnectionState state, NSDate* date) { - creating = false; - - switch (state) { - case ConnectionStateError: { - [m_controller dealloc]; - m_controller = nullptr; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); - m_isChangingState = false; - }); - return; - } - case ConnectionStateConnected: { - Q_ASSERT(date); - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); - m_isChangingState = false; - }); - return; - } - case ConnectionStateDisconnected: - [m_controller disconnect]; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); - m_isChangingState = false; - }); - return; - } - } - callback:^(BOOL a_connected) { - if (currentProto != m_protocol) { - qDebug() << "Protocols switched: " << a_connected; - return; - } - qDebug() << "State changed: " << a_connected; - if (a_connected) { - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); - m_isChangingState = false; - }); - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) -{ - static bool creating = false; - // No nested creation! - Q_ASSERT(creating == false); - creating = true; - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - QString ovpnConfig = ovpn["config"].toString(); - - m_controller = [[IOSVpnProtocolImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID - config:ovpnConfig.toNSString() - closure:^(ConnectionState state, NSDate* date) { - creating = false; - - switch (state) { - case ConnectionStateError: { - [m_controller dealloc]; - m_controller = nullptr; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); - m_isChangingState = false; - }); - return; - } - case ConnectionStateConnected: { - Q_ASSERT(date); - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); - m_isChangingState = false; - }); - return; - } - case ConnectionStateDisconnected: - // Just in case we are connecting, let's call disconnect. - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); - m_isChangingState = false; - }); - return; - } - } - callback:^(BOOL a_connected) { - if (currentProto != m_protocol) { - qDebug() << "Protocols switched: " << a_connected; - return; - } - qDebug() << "VPN State changed: " << a_connected; - if (a_connected) { - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); - m_isChangingState = false; - }); - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) -{ - static bool creating = false; - // No nested creation! - Q_ASSERT(creating == false); - creating = true; - - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - QString ovpnConfig = ovpn["config"].toString(); - - m_controller = [[IOSVpnProtocolImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID - config:ovpnConfig.toNSString() - closure:^(ConnectionState state, NSDate* date) { - qDebug() << "OVPN Creation completed with connection state:" << state; - creating = false; - - switch (state) { - case ConnectionStateError: { - [m_controller dealloc]; - m_controller = nullptr; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); - m_isChangingState = false; - }); - return; - } - case ConnectionStateConnected: { - Q_ASSERT(date); - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); - m_isChangingState = false; - }); - return; - } - case ConnectionStateDisconnected: - // Just in case we are connecting, let's call disconnect. - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); - m_isChangingState = false; - }); - return; - } - } - callback:^(BOOL a_connected) { - if (currentProto != m_protocol) { - qDebug() << "Protocols switched: " << a_connected; - return; - } - qDebug() << "VPN State changed: " << a_connected; - if (a_connected) { - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); - m_isChangingState = false; - }); - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) -{ - static bool creating = false; - // No nested creation! - Q_ASSERT(creating == false); - creating = true; - - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - QString ovpnConfig = ovpn["config"].toString(); - QJsonObject ssConfig = rawConfig["shadowsocks_config_data"].toObject(); - - m_controller = [[IOSVpnProtocolImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID - tunnelConfig:ovpnConfig.toNSString() - ssConfig:serializeSSConfig(ssConfig).toNSString() - closure:^(ConnectionState state, NSDate* date) { - creating = false; - - switch (state) { - case ConnectionStateError: { - [m_controller dealloc]; - m_controller = nullptr; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); - m_isChangingState = false; - }); - return; - } - case ConnectionStateConnected: { - Q_ASSERT(date); - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); - m_isChangingState = false; - }); - return; - } - case ConnectionStateDisconnected: - // Just in case we are connecting, let's call disconnect. - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); - m_isChangingState = false; - }); - return; - } - } - callback:^(BOOL a_connected) { - if (currentProto != m_protocol) { - qDebug() << "Protocols switched: " << a_connected; - return; - } - qDebug() << "SS State changed: " << a_connected; - if (a_connected) { - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); - m_isChangingState = false; - }); - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -void IOSVpnProtocol::launchWireguardTunnel(const QJsonObject &rawConfig) -{ - QJsonObject config = rawConfig["wireguard_config_data"].toObject(); - - QString clientPrivateKey = config["client_priv_key"].toString(); - QByteArray key = QByteArray::fromBase64(clientPrivateKey.toLocal8Bit()); - QString clientPubKey = config["client_pub_key"].toString(); - - QString addr = config["config"].toString().split("\n").takeAt(1).split(" = ").takeLast(); - QStringList dnsServersList = config["config"].toString().split("\n").takeAt(2).split(" = ").takeLast().split(", "); - QString privkey = config["config"].toString().split("\n").takeAt(3).split(" = ").takeLast(); - QString pubkey = config["config"].toString().split("\n").takeAt(6).split(" = ").takeLast(); - QString presharedkey = config["config"].toString().split("\n").takeAt(7).split(" = ").takeLast(); - QStringList allowedIPList = config["config"].toString().split("\n").takeAt(8).split(" = ").takeLast().split(", "); - QString endpoint = config["config"].toString().split("\n").takeAt(9).split(" = ").takeLast(); - QString serverAddr = config["config"].toString().split("\n").takeAt(9).split(" = ").takeLast().split(":").takeFirst(); - QString port = config["config"].toString().split("\n").takeAt(9).split(" = ").takeLast().split(":").takeLast(); - QString keepalive = config["config"].toString().split("\n").takeAt(10).split(" = ").takeLast(); - - QString hostname = config["hostName"].toString(); - QString pskKey = config["psk_key"].toString(); - QString serverPubKey = config["server_pub_key"].toString(); - - NSMutableArray* allowedIPAddressRangesNS = - [NSMutableArray arrayWithCapacity:allowedIPList.length()]; - for (const IPAddressRange item : allowedIPList) { - VPNIPAddressRange* range = - [[VPNIPAddressRange alloc] initWithAddress:item.ipAddress().toNSString() - networkPrefixLength:item.range() - isIpv6:item.type() == IPAddressRange::IPv6]; - [allowedIPAddressRangesNS addObject:[range autorelease]]; - } - - [m_controller connectWithDnsServer:dnsServersList.takeFirst().toNSString() - serverIpv6Gateway:@"FE80::1" - serverPublicKey:serverPubKey.toNSString() - presharedKey:pskKey.toNSString() - serverIpv4AddrIn:serverAddr.toNSString() - serverPort:port.toInt() - allowedIPAddressRanges:allowedIPAddressRangesNS - ipv6Enabled:NO - reason:0 - failureCallback:^() { - qDebug() << "Wireguard Protocol - connection failed"; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - - -void IOSVpnProtocol::launchCloakTunnel(const QJsonObject &rawConfig) -{ - //TODO move to OpenVpnConfigurator - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - - QString ovpnConfig = ovpn["config"].toString(); - - if(rawConfig["protocol"].toString() == "cloak"){ - QJsonObject cloak = rawConfig["cloak_config_data"].toObject(); - cloak["NumConn"] = 1; - if (cloak.contains("remote")) { - cloak["RemoteHost"] = cloak["remote"].toString(); - } - if (cloak.contains("port")) { - cloak["RemotePort"] = cloak["port"].toString(); - } - - cloak.remove("remote"); - cloak.remove("port"); - cloak.remove("transport_proto"); - - // Convert JSONObject to JSONDocument - QJsonObject jsonObject {}; - foreach(const QString& key, cloak.keys()) { - if(key == "NumConn" or key == "StreamTimeout"){ - jsonObject.insert(key, cloak.value(key).toInt()); - }else{ - jsonObject.insert(key, cloak.value(key).toString()); - } - } - QJsonDocument doc(jsonObject); - QString strJson(doc.toJson(QJsonDocument::Compact)); - - QString cloakBase64 = strJson.toUtf8().toBase64(); - ovpnConfig.append("\n\n"); - ovpnConfig.append(cloakBase64); - ovpnConfig.append("\n\n"); - } - - - [m_controller connectWithOvpnConfig:ovpnConfig.toNSString() - failureCallback:^{ - qDebug() << "IOSVPNProtocol (OpenVPN Cloak) - connection failed"; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - - - -void IOSVpnProtocol::launchOpenVPNTunnel(const QJsonObject &rawConfig) -{ - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - QString ovpnConfig = ovpn["config"].toString(); - - [m_controller connectWithOvpnConfig:ovpnConfig.toNSString() - failureCallback:^{ - qDebug() << "IOSVPNProtocol (OpenVPN) - connection failed"; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -void IOSVpnProtocol::launchShadowSocksTunnel(const QJsonObject &rawConfig) { - QJsonObject ovpn = rawConfig["openvpn_config_data"].toObject(); - QString ovpnConfig = ovpn["config"].toString(); - QJsonObject ssConfig = rawConfig["shadowsocks_config_data"].toObject(); - QString ss = serializeSSConfig(ssConfig); - - [m_controller connectWithSsConfig:ss.toNSString() - ovpnConfig:ovpnConfig.toNSString() - failureCallback:^{ - qDebug() << "IOSVPNProtocol (ShadowSocks) - connection failed"; - dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); - m_isChangingState = false; - }); - }]; -} - -QString IOSVpnProtocol::serializeSSConfig(const QJsonObject &ssConfig) { - QString ssLocalPort = ssConfig["local_port"].toString(); - QString ssMethod = ssConfig["method"].toString(); - QString ssPassword = ssConfig["password"].toString(); - QString ssServer = ssConfig["server"].toString(); - QString ssPort = ssConfig["server_port"].toString(); - QString ssTimeout = ssConfig["timeout"].toString(); - - QJsonObject shadowSocksConfig = QJsonObject(); - shadowSocksConfig.insert("local_addr", "127.0.0.1"); - shadowSocksConfig.insert("local_port", ssConfig["local_port"].toInt()); - shadowSocksConfig.insert("method", ssConfig["method"].toString()); - shadowSocksConfig.insert("password", ssConfig["password"].toString()); - shadowSocksConfig.insert("server", ssConfig["server"].toString()); - shadowSocksConfig.insert("server_port", ssConfig["server_port"].toInt()); - shadowSocksConfig.insert("timeout", ssConfig["timeout"].toInt()); - - QString ss = QString(QJsonDocument(shadowSocksConfig).toJson()); - qDebug() << "SS Config JsonString:\n" << ss; - return ss; -} diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index f7421c2d..db794100 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -63,6 +63,7 @@ constexpr char last_config[] = "last_config"; constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; constexpr char openvpn[] = "openvpn"; +constexpr char cloak[] = "cloak"; constexpr char wireguard[] = "wireguard"; } diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp index 951ec8b8..044b97b8 100644 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ b/client/ui/pages_logic/AppSettingsLogic.cpp @@ -10,6 +10,10 @@ #include #include +#ifdef Q_OS_IOS +#include +#endif + using namespace amnezia; using namespace PageEnumNS; @@ -89,6 +93,18 @@ void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() if (fileName.isEmpty()) return; QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), + fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index f7b685ea..c1670b70 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -18,6 +18,10 @@ #include "../../platforms/android/android_controller.h" #endif +#ifdef Q_OS_IOS +#include +#endif + namespace { enum class ConfigTypes { Amnezia, @@ -189,6 +193,18 @@ void StartPageLogic::onPushButtonImportOpenFile() if (fileName.isEmpty()) return; QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), + fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 9f56ad97..1fc0b7f5 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -23,7 +23,7 @@ #endif #ifdef Q_OS_IOS -#include +#include "platforms/ios/ios_controller.h" #endif #include "utilities.h" @@ -32,17 +32,22 @@ VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, QObject* parent) : QObject(parent), m_settings(settings), - m_configurator(configurator) + m_configurator(configurator), + m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); +#ifdef Q_OS_IOS + connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); + connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); + +#endif } VpnConnection::~VpnConnection() { - if (m_vpnProtocol != nullptr) { - m_vpnProtocol->deleteLater(); - m_vpnProtocol.clear(); - } +#if defined AMNEZIA_DESKTOP + disconnectFromVpn(); +#endif } void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) @@ -278,6 +283,13 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, vpnConfiguration[config_key::dns1] = dns.first; vpnConfiguration[config_key::dns2] = dns.second; + const QJsonObject &server = m_settings->server(serverIndex); + vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString(); + vpnConfiguration[config_key::description] = server.value(config_key::description).toString(); + + // TODO: try to get hostName, port, description for 3rd party configs + // vpnConfiguration[config_key::port] = ...; + return vpnConfiguration; } @@ -285,8 +297,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) { qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") - .arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode(); - + .arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode(); #if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); @@ -305,11 +316,13 @@ void VpnConnection::connectToVpn(int serverIndex, m_remoteAddress = credentials.hostName; emit connectionStateChanged(VpnProtocol::Connecting); +#ifdef AMNEZIA_DESKTOP if (m_vpnProtocol) { disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); m_vpnProtocol->stop(); m_vpnProtocol.reset(); } +#endif ErrorCode e = ErrorCode::NoError; @@ -333,18 +346,9 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnProtocol.reset(androidVpnProtocol); #elif defined Q_OS_IOS Proto proto = ContainerProps::defaultProtocol(container); - auto iosVpnProtocol = new IOSVpnProtocol(proto, m_vpnConfiguration); - - if (!iosVpnProtocol->initialize()) { - qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; - iosVpnProtocol->deleteLater(); - return; - } - - connect(&m_checkTimer, &QTimer::timeout, iosVpnProtocol, &IOSVpnProtocol::checkStatus); - m_vpnProtocol.reset(iosVpnProtocol); - + IosController::Instance()->connectVpn(proto, m_vpnConfiguration); + connect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); + return; #endif createProtocolConnections(); @@ -415,6 +419,11 @@ void VpnConnection::disconnectFromVpn() AndroidController::instance()->stop(); #endif +#ifdef Q_OS_IOS + IosController::Instance()->disconnectVpn(); + disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus); +#endif + if (!m_vpnProtocol.data()) { emit connectionStateChanged(VpnProtocol::Disconnected); return; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index abef58a6..0c3c9a5f 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -11,9 +11,6 @@ #include "core/defs.h" #include "settings.h" -#ifdef Q_OS_IOS -#include "protocols/ios_vpnprotocol.h" -#endif #ifdef AMNEZIA_DESKTOP #include "core/ipcclient.h"