Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/new-gui
This commit is contained in:
commit
36a2482165
32 changed files with 948 additions and 3192 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9a3cf57a1166c634d43e75f00ff6072938aea5cb
|
Subproject commit 75ab7e2418b83af7f8ed0a448ec5081b37b54442
|
||||||
|
|
@ -233,7 +233,7 @@ if(APPLE)
|
||||||
set(BUILD_IOS_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "iOS Application identifier")
|
set(BUILD_IOS_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "iOS Application identifier")
|
||||||
endif()
|
endif()
|
||||||
if(NOT BUILD_IOS_GROUP_IDENTIFIER)
|
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()
|
endif()
|
||||||
if(NOT BUILD_VPN_DEVELOPMENT_TEAM)
|
if(NOT BUILD_VPN_DEVELOPMENT_TEAM)
|
||||||
set(BUILD_VPN_DEVELOPMENT_TEAM X7UJ388FXK CACHE STRING "Amnezia VPN Development Team")
|
set(BUILD_VPN_DEVELOPMENT_TEAM X7UJ388FXK CACHE STRING "Amnezia VPN Development Team")
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@
|
||||||
#include "protocols/qml_register_protocols.h"
|
#include "protocols/qml_register_protocols.h"
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
#if defined(Q_OS_IOS)
|
||||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
|
@ -107,6 +108,7 @@ void AmneziaApplication::init()
|
||||||
&ImportController::extractConfigFromData);
|
&ImportController::extractConfigFromData);
|
||||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(),
|
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(),
|
||||||
&PageController::goToPageViewConfig);
|
&PageController::goToPageViewConfig);
|
||||||
|
IosController::Instance()->initialize();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
|
||||||
|
|
@ -18,51 +18,35 @@ find_library(FW_STOREKIT StoreKit)
|
||||||
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||||
|
|
||||||
set(LIBS ${LIBS}
|
set(LIBS ${LIBS}
|
||||||
${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT}
|
${FW_AUTHENTICATIONSERVICES}
|
||||||
${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT}
|
${FW_UIKIT}
|
||||||
|
${FW_AVFOUNDATION}
|
||||||
|
${FW_FOUNDATION}
|
||||||
|
${FW_STOREKIT}
|
||||||
${FW_USERNOTIFICATIONS}
|
${FW_USERNOTIFICATIONS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
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}/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.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.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}
|
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/iosnotificationhandler.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.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/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.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})
|
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
|
@ -112,23 +96,26 @@ target_compile_options(${PROJECT} PRIVATE
|
||||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
-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
|
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}/platforms/ios/ioslogger.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift
|
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardApp/LocalizationHelper.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift
|
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c
|
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,23 @@
|
||||||
message("MAC build")
|
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_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
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_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||||
|
|
||||||
|
target_compile_options(${PROJECT} PRIVATE
|
||||||
|
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||||
find_library(FW_SYSTEMCONFIG SystemConfiguration)
|
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||||
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})
|
|
||||||
|
|
||||||
# Get SDK path
|
# Get SDK path
|
||||||
execute_process(
|
execute_process(
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ bool Logger::init()
|
||||||
m_file.setTextModeEnabled(true);
|
m_file.setTextModeEnabled(true);
|
||||||
m_textStream.setDevice(&m_file);
|
m_textStream.setDevice(&m_file);
|
||||||
|
|
||||||
#ifndef QT_DEBUG
|
#if !defined(QT_DEBUG) || defined(Q_OS_IOS)
|
||||||
qInstallMessageHandler(debugMessageHandler);
|
qInstallMessageHandler(debugMessageHandler);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
84
client/platforms/ios/ios_controller.h
Normal file
84
client/platforms/ios/ios_controller.h
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#ifndef IOS_CONTROLLER_H
|
||||||
|
#define IOS_CONTROLLER_H
|
||||||
|
|
||||||
|
#include "protocols/vpnprotocol.h"
|
||||||
|
|
||||||
|
#ifdef __OBJC__
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
@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<void (const QString &)> &&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<void(NSDictionary*)> callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
amnezia::Proto m_proto;
|
||||||
|
QJsonObject m_rawConfig;
|
||||||
|
QString m_tunnelId;
|
||||||
|
uint64_t m_txBytes;
|
||||||
|
uint64_t m_rxBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IOS_CONTROLLER_H
|
||||||
475
client/platforms/ios/ios_controller.mm
Normal file
475
client/platforms/ios/ios_controller.mm
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
#include "ios_controller.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "../protocols/vpnprotocol.h"
|
||||||
|
#import "ios_controller_wrapper.h"
|
||||||
|
|
||||||
|
#import <NetworkExtension/NetworkExtension.h>
|
||||||
|
#import <NetworkExtension/NETunnelProviderManager.h>
|
||||||
|
#import <NetworkExtension/NEVPNManager.h>
|
||||||
|
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||||
|
|
||||||
|
|
||||||
|
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<NETunnelProviderManager *> * _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<NETunnelProviderManager *> * _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<cloak>\n");
|
||||||
|
ovpnConfig.append(cloakBase64);
|
||||||
|
ovpnConfig.append("\n</cloak>\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<void(NSDictionary*)> 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
client/platforms/ios/ios_controller_wrapper.h
Normal file
15
client/platforms/ios/ios_controller_wrapper.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#import <NetworkExtension/NetworkExtension.h>
|
||||||
|
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
class IosController;
|
||||||
|
|
||||||
|
@interface IosControllerWrapper : NSObject {
|
||||||
|
IosController *cppController;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithCppController:(IosController *)controller;
|
||||||
|
- (void)vpnStatusDidChange:(NSNotification *)notification;
|
||||||
|
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
||||||
|
|
||||||
|
@end
|
||||||
28
client/platforms/ios/ios_controller_wrapper.mm
Normal file
28
client/platforms/ios/ios_controller_wrapper.mm
Normal file
|
|
@ -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
|
||||||
|
|
@ -181,11 +181,6 @@ EXPORT bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN
|
||||||
// Logging functions
|
// Logging functions
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
||||||
#ifndef NETWORK_EXTENSION
|
|
||||||
namespace {
|
|
||||||
//Logger logger(LOG_IOS, "IOSSGlue");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
EXPORT void write_msg_to_log(const char* tag, const char* msg) {
|
EXPORT void write_msg_to_log(const char* tag, const char* msg) {
|
||||||
#ifndef NETWORK_EXTENSION
|
#ifndef NETWORK_EXTENSION
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -17,7 +17,12 @@ public class Logger {
|
||||||
deinit {}
|
deinit {}
|
||||||
|
|
||||||
func log(message: String) {
|
func log(message: String) {
|
||||||
write_msg_to_log(tag, message.trimmingCharacters(in: .newlines))
|
let suiteName = "group.org.amnezia.AmneziaVPN"
|
||||||
|
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 {
|
func writeLog(to targetFile: String) -> Bool {
|
||||||
|
|
@ -37,8 +42,7 @@ public class Logger {
|
||||||
appVersion += " (\(appBuild))"
|
appVersion += " (\(appBuild))"
|
||||||
}
|
}
|
||||||
|
|
||||||
let goBackendVersion = "1"
|
Logger.global?.log(message: "App version: \(appVersion)")
|
||||||
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,5 +54,4 @@ func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
|
||||||
func wg_log(_ type: OSLogType, message msg: String) {
|
func wg_log(_ type: OSLogType, message msg: String) {
|
||||||
os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
|
os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
|
||||||
Logger.global?.log(message: msg)
|
Logger.global?.log(message: msg)
|
||||||
NSLog("AMNEZIA: \(msg)")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import NetworkExtension
|
||||||
import os
|
import os
|
||||||
import Darwin
|
import Darwin
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
//import Tun2socks
|
|
||||||
enum TunnelProtoType: String {
|
enum TunnelProtoType: String {
|
||||||
case wireguard, openvpn, shadowsocks, none
|
case wireguard, openvpn, shadowsocks, none
|
||||||
}
|
}
|
||||||
|
|
@ -11,25 +11,19 @@ enum TunnelProtoType: String {
|
||||||
struct Constants {
|
struct Constants {
|
||||||
static let kDefaultPathKey = "defaultPath"
|
static let kDefaultPathKey = "defaultPath"
|
||||||
static let processQueueName = "org.amnezia.process-packets"
|
static let processQueueName = "org.amnezia.process-packets"
|
||||||
static let ssQueueName = "org.amnezia.shadowsocks"
|
|
||||||
static let kActivationAttemptId = "activationAttemptId"
|
static let kActivationAttemptId = "activationAttemptId"
|
||||||
static let ovpnConfigKey = "ovpn"
|
static let ovpnConfigKey = "ovpn"
|
||||||
static let ssConfigKey = "ss"
|
static let wireGuardConfigKey = "wireguard"
|
||||||
static let loggerTag = "NET"
|
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 kActionStart = "start"
|
||||||
static let kActionRestart = "restart"
|
static let kActionRestart = "restart"
|
||||||
static let kActionStop = "stop"
|
static let kActionStop = "stop"
|
||||||
static let kActionGetTunnelId = "getTunnelId"
|
static let kActionGetTunnelId = "getTunnelId"
|
||||||
|
static let kActionStatus = "status"
|
||||||
static let kActionIsServerReachable = "isServerReachable"
|
static let kActionIsServerReachable = "isServerReachable"
|
||||||
static let kMessageKeyAction = "action"
|
static let kMessageKeyAction = "action"
|
||||||
static let kMessageKeyTunnelid = "tunnelId"
|
static let kMessageKeyTunnelId = "tunnelId"
|
||||||
static let kMessageKeyConfig = "config"
|
static let kMessageKeyConfig = "config"
|
||||||
static let kMessageKeyErrorCode = "errorCode"
|
static let kMessageKeyErrorCode = "errorCode"
|
||||||
static let kMessageKeyHost = "host"
|
static let kMessageKeyHost = "host"
|
||||||
|
|
@ -37,8 +31,6 @@ struct Constants {
|
||||||
static let kMessageKeyOnDemand = "is-on-demand"
|
static let kMessageKeyOnDemand = "is-on-demand"
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias ShadowsocksProxyCompletion = ((Int32, NSError?) -> Void)?
|
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private lazy var wgAdapter: WireGuardAdapter = {
|
private lazy var wgAdapter: WireGuardAdapter = {
|
||||||
|
|
@ -53,83 +45,121 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
return adapter
|
return adapter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var shadowSocksConfig: Data? = nil
|
/// Internal queue.
|
||||||
private var openVPNConfig: Data? = nil
|
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||||
var ssCompletion: ShadowsocksProxyCompletion = nil
|
|
||||||
|
|
||||||
// private var ssProvider: ShadowSocksTunnel? = nil
|
private var openVPNConfig: Data? = 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?
|
|
||||||
|
|
||||||
let vpnReachability = OpenVPNReachability()
|
let vpnReachability = OpenVPNReachability()
|
||||||
|
|
||||||
var startHandler: ((Error?) -> Void)?
|
var startHandler: ((Error?) -> Void)?
|
||||||
var stopHandler: (() -> 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)
|
Logger.configureGlobal(tagged: Constants.loggerTag, withFilePath: FileManager.logFileURL?.path)
|
||||||
|
Logger.global?.log(message: "Init NEPacketTunnelProvider")
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||||
let _: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data {
|
Logger.global?.log(message: "Failed to serialize message from app")
|
||||||
let withoutShadowSocks = providerConfiguration[Constants.ssConfigKey] as? Data == nil
|
return
|
||||||
protoType = withoutShadowSocks ? .openvpn : .shadowsocks
|
|
||||||
} else {
|
|
||||||
protoType = .wireguard
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch protoType {
|
guard let completionHandler = completionHandler else {
|
||||||
case .wireguard:
|
Logger.global?.log(message: "Missing message completion handler")
|
||||||
startWireguard(activationAttemptId: activationAttemptId,
|
return
|
||||||
errorNotifier: errorNotifier,
|
}
|
||||||
completionHandler: completionHandler)
|
|
||||||
case .openvpn:
|
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||||
startOpenVPN(completionHandler: completionHandler)
|
Logger.global?.log(message: "Missing action key in app message")
|
||||||
case .shadowsocks:
|
completionHandler(nil)
|
||||||
break
|
return
|
||||||
// startShadowSocks(completionHandler: completionHandler)
|
}
|
||||||
case .none:
|
|
||||||
break
|
if action == Constants.kActionStatus {
|
||||||
|
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
switch protoType {
|
dispatchQueue.async {
|
||||||
case .wireguard:
|
|
||||||
stopWireguard(with: reason, completionHandler: completionHandler)
|
switch self.protoType {
|
||||||
case .openvpn:
|
case .wireguard:
|
||||||
stopOpenVPN(with: reason, completionHandler: completionHandler)
|
self.stopWireguard(with: reason, completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
case .openvpn:
|
||||||
break
|
self.stopOpenVPN(with: reason, completionHandler: completionHandler)
|
||||||
// stopShadowSocks(with: reason, completionHandler: completionHandler)
|
case .shadowsocks:
|
||||||
case .none:
|
break
|
||||||
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 {
|
switch protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
|
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .openvpn:
|
case .openvpn:
|
||||||
handleOpenVPNAppMessage(messageData, completionHandler: completionHandler)
|
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
case .shadowsocks:
|
||||||
break
|
break
|
||||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,21 +167,29 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private func startWireguard(activationAttemptId: String?,
|
private func startWireguard(activationAttemptId: String?,
|
||||||
errorNotifier: ErrorNotifier,
|
errorNotifier: ErrorNotifier,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let tunnelConfiguration = tunnelProviderProtocol.asTunnelConfiguration() else {
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||||
return
|
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"))
|
wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||||
guard let adapterError = adapterError else {
|
guard let adapterError = adapterError else {
|
||||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||||
|
|
||||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||||
|
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -197,24 +235,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
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) {
|
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||||
|
|
||||||
|
|
@ -242,16 +263,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
ovpnAdapter.disconnect()
|
ovpnAdapter.disconnect()
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
private func stopShadowSocks(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
stopOpenVPN(with: reason) { [weak self] in
|
guard let completionHandler = completionHandler else { return }
|
||||||
guard let `self` = self else { return }
|
wgAdapter.getRuntimeConfiguration { settings in
|
||||||
// self.stopSSProvider(completionHandler: completionHandler)
|
var data: Data?
|
||||||
// self.stopTun2SocksTunnel(completionHandler: completionHandler)
|
if let settings = settings {
|
||||||
self.stopLeafRedirector(completion: completionHandler)
|
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) {
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
if messageData.count == 1 && messageData[0] == 0 {
|
||||||
|
|
@ -296,398 +335,24 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleOpenVPNAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
|
||||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||||
let strBytesin = "rx_bytes=" + String(bytesin);
|
|
||||||
|
|
||||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||||
let strBytesout = "tx_bytes=" + String(bytesout);
|
|
||||||
|
|
||||||
let strData = strBytesin + "\n" + strBytesout;
|
let response: [String: Any] = [
|
||||||
let data = Data(strData.utf8)
|
"rx_bytes" : bytesin,
|
||||||
completionHandler(data)
|
"tx_bytes" : bytesout
|
||||||
}
|
]
|
||||||
|
|
||||||
|
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)
|
// TODO review
|
||||||
}
|
|
||||||
*/
|
|
||||||
// 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<NSError?>? = 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<NSError?>? = 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<CChar>? {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
|
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 str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
let configuration = OpenVPNConfiguration()
|
||||||
|
|
@ -726,26 +391,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
// MARK: -- Network observing methods
|
// 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() {
|
private func startListeningForNetworkChanges() {
|
||||||
stopListeningForNetworkChanges()
|
stopListeningForNetworkChanges()
|
||||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||||
|
|
@ -779,23 +424,43 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension WireGuardLogLevel {
|
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||||
var osLogLevel: OSLogType {
|
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||||
switch self {
|
|
||||||
case .verbose:
|
let emptyTunnelConfiguration = TunnelConfiguration(
|
||||||
return .debug
|
name: nil,
|
||||||
case .error:
|
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||||
return .error
|
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: OpenVPNAdapterPacketFlow {}
|
||||||
|
|
||||||
/* extension NEPacketTunnelFlow: ShadowSocksAdapterPacketFlow {} */
|
|
||||||
|
|
||||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
|
|
||||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||||
|
|
@ -873,14 +538,14 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
wg_log(.info, message: logMessage)
|
wg_log(.info, message: logMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
extension PacketTunnelProvider: Tun2socksTunWriterProtocol {
|
extension WireGuardLogLevel {
|
||||||
func write(_ p0: Data?, n: UnsafeMutablePointer<Int>?) throws {
|
var osLogLevel: OSLogType {
|
||||||
if let packets = p0 {
|
switch self {
|
||||||
self.packetFlow.writePackets([packets], withProtocols: [NSNumber(value: AF_INET)])
|
case .verbose:
|
||||||
|
return .debug
|
||||||
|
case .error:
|
||||||
|
return .error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func close() throws {}
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
||||||
|
|
@ -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<VPNIPAddressRange>, 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<ifaddrs>?
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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> IPAddressRange::fromIPAddressList(
|
|
||||||
const QList<IPAddress>& list) {
|
|
||||||
QList<IPAddressRange> result;
|
|
||||||
for (const IPAddress& ip : list) {
|
|
||||||
result.append(IPAddressRange(ip.toString()));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -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 <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
class IPAddress;
|
|
||||||
|
|
||||||
class IPAddressRange final {
|
|
||||||
public:
|
|
||||||
enum IPAddressType {
|
|
||||||
IPv4,
|
|
||||||
IPv6,
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList<IPAddressRange> fromIPAddressList(const QList<IPAddress>& 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
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@protocol ShadowSocksAdapterPacketFlow <NSObject>
|
|
||||||
|
|
||||||
- (void)readPacketsWithCompletionHandler:(void (^)(NSArray<NSData *> *packets, NSArray<NSNumber *> *protocols))completionHandler;
|
|
||||||
- (BOOL)writePackets:(NSArray<NSData *> *)packets withProtocols:(NSArray<NSNumber *> *)protocols;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#ifndef ShadowsocksConnectivity_h
|
|
||||||
#define ShadowsocksConnectivity_h
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
/**
|
|
||||||
* 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 */
|
|
||||||
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
#import "./ssconnectivity.h"
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
//#import <ShadowSocks/shadowsocks.h>
|
|
||||||
|
|
||||||
@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 ()<GCDAsyncSocketDelegate, GCDAsyncUdpSocketDelegate>
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#ifndef sspacket_h
|
|
||||||
#define sspacket_h
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
@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 */
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
#import <sspacket.h>
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
@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
|
|
||||||
|
|
@ -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<SSProvider>.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<SSProvider>.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<CChar>,
|
|
||||||
let remotePort = ssConfig["server_port"] as? Int32,
|
|
||||||
let localAddress = ssConfig["local_addr"] as? String, //UnsafeMutablePointer<CChar>,
|
|
||||||
let localPort = ssConfig["local_port"] as? Int32,
|
|
||||||
let method = ssConfig["method"] as? String, //UnsafeMutablePointer<CChar>,
|
|
||||||
let password = ssConfig["password"] as? String,//UnsafeMutablePointer<CChar>,
|
|
||||||
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<profile_t>.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<SSProvider>.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Int>?) throws {
|
|
||||||
if let packets = p0 {
|
|
||||||
tunnelFlow.writePackets([packets], withProtocols: [NSNumber(value: PF_INET)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func close() throws {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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<CChar>
|
|
||||||
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<CChar>) {
|
|
||||||
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<TunProvider>.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<CChar>, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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<void(const QString&)>&& 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<void(const QString&)> 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
|
|
||||||
|
|
@ -1,662 +0,0 @@
|
||||||
#include <QDebug>
|
|
||||||
#include <QHostAddress>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QRandomGenerator>
|
|
||||||
#include <QTextCodec>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
|
|
||||||
#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(Vpn::ConnectionState::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<IOSVpnProtocol> 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<void (const QString &)> &&callback)
|
|
||||||
{
|
|
||||||
std::function<void(const QString&)> 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(Vpn::ConnectionState::Error);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ConnectionStateConnected: {
|
|
||||||
Q_ASSERT(date);
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ConnectionStateDisconnected:
|
|
||||||
[m_controller disconnect];
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::Connected);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::Error);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ConnectionStateConnected: {
|
|
||||||
Q_ASSERT(date);
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::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(Vpn::ConnectionState::Connected);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::Error);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ConnectionStateConnected: {
|
|
||||||
Q_ASSERT(date);
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::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(Vpn::ConnectionState::Connected);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::Error);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ConnectionStateConnected: {
|
|
||||||
Q_ASSERT(date);
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::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(Vpn::ConnectionState::Connected);
|
|
||||||
m_isChangingState = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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<VPNIPAddressRange*>* allowedIPAddressRangesNS =
|
|
||||||
[NSMutableArray<VPNIPAddressRange*> 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(Vpn::ConnectionState::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<cloak>\n");
|
|
||||||
ovpnConfig.append(cloakBase64);
|
|
||||||
ovpnConfig.append("\n</cloak>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[m_controller connectWithOvpnConfig:ovpnConfig.toNSString()
|
|
||||||
failureCallback:^{
|
|
||||||
qDebug() << "IOSVPNProtocol (OpenVPN Cloak) - connection failed";
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::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(Vpn::ConnectionState::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(Vpn::ConnectionState::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;
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <utilities.h>
|
#include <utilities.h>
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
using namespace PageEnumNS;
|
using namespace PageEnumNS;
|
||||||
|
|
||||||
|
|
@ -89,6 +93,18 @@ void AppSettingsLogic::onPushButtonRestoreAppConfigClicked()
|
||||||
if (fileName.isEmpty()) return;
|
if (fileName.isEmpty()) return;
|
||||||
|
|
||||||
QFile file(fileName);
|
QFile file(fileName);
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
CFURLRef url = CFURLCreateWithFileSystemPath(
|
||||||
|
kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()),
|
||||||
|
fileName.length()),
|
||||||
|
kCFURLPOSIXPathStyle, 0);
|
||||||
|
|
||||||
|
if (!CFURLStartAccessingSecurityScopedResource(url)) {
|
||||||
|
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
file.open(QIODevice::ReadOnly);
|
file.open(QIODevice::ReadOnly);
|
||||||
QByteArray data = file.readAll();
|
QByteArray data = file.readAll();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,18 @@
|
||||||
#include <QJniObject>
|
#include <QJniObject>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace
|
#ifdef Q_OS_IOS
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
enum class ConfigTypes {
|
||||||
|
Amnezia,
|
||||||
|
OpenVpn,
|
||||||
|
WireGuard
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigTypes checkConfigFormat(const QString &config)
|
||||||
{
|
{
|
||||||
enum class ConfigTypes {
|
enum class ConfigTypes {
|
||||||
Amnezia,
|
Amnezia,
|
||||||
|
|
@ -189,6 +200,18 @@ void StartPageLogic::onPushButtonImportOpenFile()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QFile file(fileName);
|
QFile file(fileName);
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
CFURLRef url = CFURLCreateWithFileSystemPath(
|
||||||
|
kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()),
|
||||||
|
fileName.length()),
|
||||||
|
kCFURLPOSIXPathStyle, 0);
|
||||||
|
|
||||||
|
if (!CFURLStartAccessingSecurityScopedResource(url)) {
|
||||||
|
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
file.open(QIODevice::ReadOnly);
|
file.open(QIODevice::ReadOnly);
|
||||||
QByteArray data = file.readAll();
|
QByteArray data = file.readAll();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ void UiLogic::showOnStartup()
|
||||||
void UiLogic::onUpdateAllPages()
|
void UiLogic::onUpdateAllPages()
|
||||||
{
|
{
|
||||||
for (auto logic : m_logicMap) {
|
for (auto logic : m_logicMap) {
|
||||||
if (dynamic_cast<ClientInfoLogic *>(logic) || dynamic_cast<ClientManagementLogic *>(logic)) {
|
if (dynamic_cast<ClientInfoLogic*>(logic) || dynamic_cast<ClientManagementLogic*>(logic) || dynamic_cast<QrDecoderLogic*>(logic)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
logic->onUpdatePage();
|
logic->onUpdatePage();
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
#include <protocols/ios_vpnprotocol.h>
|
#include "platforms/ios/ios_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
@ -32,17 +32,22 @@
|
||||||
VpnConnection::VpnConnection(std::shared_ptr<Settings> settings,
|
VpnConnection::VpnConnection(std::shared_ptr<Settings> settings,
|
||||||
std::shared_ptr<VpnConfigurator> configurator, QObject* parent) : QObject(parent),
|
std::shared_ptr<VpnConfigurator> configurator, QObject* parent) : QObject(parent),
|
||||||
m_settings(settings),
|
m_settings(settings),
|
||||||
m_configurator(configurator)
|
m_configurator(configurator),
|
||||||
|
m_checkTimer(new QTimer(this))
|
||||||
{
|
{
|
||||||
m_checkTimer.setInterval(1000);
|
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()
|
VpnConnection::~VpnConnection()
|
||||||
{
|
{
|
||||||
if (m_vpnProtocol != nullptr) {
|
#if defined AMNEZIA_DESKTOP
|
||||||
m_vpnProtocol->deleteLater();
|
disconnectFromVpn();
|
||||||
m_vpnProtocol.clear();
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes)
|
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::dns1] = dns.first;
|
||||||
vpnConfiguration[config_key::dns2] = dns.second;
|
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;
|
return vpnConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,8 +297,7 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
|
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig)
|
||||||
{
|
{
|
||||||
qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is")
|
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 !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS)
|
||||||
if (!m_IpcClient) {
|
if (!m_IpcClient) {
|
||||||
m_IpcClient = new IpcClient(this);
|
m_IpcClient = new IpcClient(this);
|
||||||
|
|
@ -305,11 +316,13 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||||
m_remoteAddress = credentials.hostName;
|
m_remoteAddress = credentials.hostName;
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Connecting);
|
emit connectionStateChanged(Vpn::ConnectionState::Connecting);
|
||||||
|
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (m_vpnProtocol) {
|
if (m_vpnProtocol) {
|
||||||
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
m_vpnProtocol->stop();
|
m_vpnProtocol->stop();
|
||||||
m_vpnProtocol.reset();
|
m_vpnProtocol.reset();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
|
|
||||||
|
|
@ -333,18 +346,9 @@ void VpnConnection::connectToVpn(int serverIndex,
|
||||||
m_vpnProtocol.reset(androidVpnProtocol);
|
m_vpnProtocol.reset(androidVpnProtocol);
|
||||||
#elif defined Q_OS_IOS
|
#elif defined Q_OS_IOS
|
||||||
Proto proto = ContainerProps::defaultProtocol(container);
|
Proto proto = ContainerProps::defaultProtocol(container);
|
||||||
auto iosVpnProtocol = new IOSVpnProtocol(proto, m_vpnConfiguration);
|
IosController::Instance()->connectVpn(proto, m_vpnConfiguration);
|
||||||
|
connect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
|
||||||
if (!iosVpnProtocol->initialize()) {
|
return;
|
||||||
qDebug() << QString("Init failed") ;
|
|
||||||
emit Vpn::ConnectionState::Error;
|
|
||||||
iosVpnProtocol->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(&m_checkTimer, &QTimer::timeout, iosVpnProtocol, &IOSVpnProtocol::checkStatus);
|
|
||||||
m_vpnProtocol.reset(iosVpnProtocol);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
createProtocolConnections();
|
createProtocolConnections();
|
||||||
|
|
@ -414,8 +418,16 @@ void VpnConnection::disconnectFromVpn()
|
||||||
if (!m_vpnProtocol.data()) {
|
if (!m_vpnProtocol.data()) {
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
AndroidController::instance()->stop();
|
AndroidController::instance()->stop();
|
||||||
#endif
|
#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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,6 @@
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
#include "protocols/ios_vpnprotocol.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
#include "core/ipcclient.h"
|
#include "core/ipcclient.h"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue