Compare commits

..

80 Commits

Author SHA1 Message Date
Nethius a6ca1b12da moved protocol config generation to VpnConfigirationsController (#665)
Moved protocol config generation to VpnConfigurationsController
2024-04-01 14:20:02 +01:00
pokamest 82a9e7e27d Merge pull request #720 from amnezia-vpn/feature/app-split-tunneling-page-home
Added app split tunneling on home page
2024-04-01 13:33:10 +01:00
vladimir.kuznetsov f5301e1315 added app split tunneling on home page 2024-04-01 17:07:33 +05:00
Nethius adab30fc81 feature/app-split-tunneling (#702)
App Split Tunneling for Windows and Android
2024-04-01 12:45:00 +01:00
pokamest e7bd24f065 Merge pull request #718 from amnezia-vpn/bugfix/cancel-button-on-install-page
fixed display of cancel button on install/uninstall pages
2024-03-31 16:03:32 +01:00
pokamest 2ec448ba13 Merge pull request #717 from amnezia-vpn/feature/page-home-drawer
changed the way the drawer is displayed on the pageHome
2024-03-31 12:15:21 +01:00
albexk c6e6f2ae84 Add a function that minimizes the Android app (#692)
Add a function that minimizes the Android app
2024-03-31 12:14:12 +01:00
vladimir.kuznetsov e9468a4c2f fixed display of cancel button on install/uninstall pages 2024-03-31 12:40:42 +05:00
vladimir.kuznetsov 45de951897 changed the way the drawer is displayed on the pageHome 2024-03-30 16:10:37 +05:00
pokamest db8d966fac Merge pull request #714 from amnezia-vpn/bugfix/wg-and-xray-remove-button 2024-03-29 09:40:13 +00:00
pokamest 6b69bc9618 Tiny fixes 2024-03-28 17:13:48 +00:00
pokamest 0089b0b799 Error code 206 description 2024-03-28 15:01:17 +00:00
vladimir.kuznetsov e4841e809b fixed remove button for wireguard and xray settings page 2024-03-28 15:33:23 +05:00
Mykola Baibuz ba4237f1dd Xray with Reality protocol (#494)
* Xray with Reality for desktops
2024-03-27 11:02:34 +00:00
pokamest f6acec53c0 Merge pull request #712 from amnezia-vpn/bugfix/page-home-recursive-rearrange
fixed recursive rearrange on PageHome
2024-03-27 10:59:01 +00:00
Shehab Ahmed 5f631eaa76 Refactoring/change application text (#687)
Changing some texts
2024-03-26 18:05:04 +00:00
albexk 7730dd510c Add error handling of enabled "always-on" during VPN connection (#698)
* Always add awg-go version to the log
* Display an error message always when no vpn permission is granted
2024-03-25 23:09:50 +00:00
pokamest 30bd264f17 Merge pull request #711 from amnezia-vpn/bugfix/anchors-page-home-warning
fixed anchors warning on PageHome
2024-03-25 18:38:20 +00:00
vladimir.kuznetsov 5206665fa0 fixed recursive rearrange on PageHome 2024-03-25 22:30:44 +05:00
vladimir.kuznetsov 073491ccb4 fixed anchors warning on PageHome 2024-03-25 21:52:38 +05:00
pokamest 561b62cd40 Merge pull request #705 from amnezia-vpn/bugfix/import-error-handling
fixed error handling for config import
2024-03-23 13:23:31 +00:00
pokamest 1284ed4d84 Merge pull request #706 from amnezia-vpn/translations/connection-label-fix
Fix connection button labels
2024-03-23 00:30:10 +00:00
Andrey Zaharow 6f34443191 Fix connection button labels 2024-03-21 21:34:51 +01:00
vladimir.kuznetsov 02f186c54e fixed error handling for config import 2024-03-21 23:32:11 +05:00
alexeyq2 784c6cf585 Fix AWG/WG on Linux - IPv6 gateway address is ULA now (#701) 2024-03-21 15:03:00 +00:00
pokamest 14f132e127 Merge pull request #703 from amnezia-vpn/feature/linux-ipc-fix
Increase timeout for IPC command
2024-03-21 13:29:14 +00:00
Mykola Baibuz 9cb624e681 Increase timeout for IPC command 2024-03-20 23:10:29 +02:00
isamnezia 516e3da7e2 Fix open log crash and side log improvements (#694)
Fix open log crash
2024-03-20 15:35:36 +00:00
Andrey Zaharow 0e83586cae Fix UI for Burmese language (#682)
* Fix UI for Burmese language
2024-03-20 15:20:09 +00:00
Nethius 95bdae68f4 Auto disable logs after 14 days (#610)
Auto disable logs after 14 days
2024-03-20 14:22:29 +00:00
pokamest 294778884b Merge pull request #691 from amnezia-vpn/bugfix/credentials-space-check
fixed checking credentials for spaces
2024-03-18 14:37:35 +00:00
albexk 10caecbffd Fix wg reconnection problem after awg connection (#696)
* Update Android AWG to 0.2.5
2024-03-18 11:20:01 +00:00
pokamest 553a6a73dd Merge pull request #697 from amnezia-vpn/bugfix/Service-crash-after-disconnecting
ISSUE: Service is crashed after disconnecting
2024-03-18 10:52:25 +00:00
Mykola Baibuz e646b85e56 Setup MTU for WG/AWG protocol (#576)
Setup MTU for AWG/WG protocol
2024-03-18 10:41:53 +00:00
Dan Nguyen b7c513c05f ISSUE: Service is crashed after disconnecting
ROOT CAUSE: When disconnecting service, m_logworker is deleted in thread which does not have affinity with m_logworker.
			The time m_logworker is deleted, it may be used by m_logthread and make the service crashed

ACTION: Connect signal finished() of m_logthread to deleteLater() slot of m_logworker to safety delete it.
2024-03-17 07:09:57 +07:00
pokamest 9f82b4c21f Merge pull request #689 from amnezia-vpn/translations/burmese-fix
Shortening of translated text in Burmese
2024-03-16 20:32:37 +00:00
pokamest 02b2da38cf Merge pull request #690 from amnezia-vpn/bugfix/native-config-import-error-handling
added error handling for importing a native config
2024-03-14 17:03:06 +00:00
vladimir.kuznetsov f51077b2be fixed checking credentials for spaces 2024-03-14 15:59:16 +05:00
vladimir.kuznetsov 33f49bfddb added error handling for importing a native config 2024-03-14 12:55:33 +05:00
Andrey Zaharow 9a81f13f81 Short translated text 2024-03-13 22:44:09 +01:00
albexk 915fb6759a Add Android openssl3 libs, fix https connection error (#685)
Add Android openssl3 libs, fix https connection error
2024-03-13 21:22:56 +00:00
Nethius c5a5bfde69 extended the validation of the contents of the imported file (#670)
Extended the validation of the contents of the imported file
2024-03-13 21:22:10 +00:00
Andrey Zaharow 0a90fd110d Add RU translation for Error 1101 text (#683)
* Add RU translation for Error 1101 text
2024-03-12 23:17:18 +00:00
pokamest 541d6eb0b8 Merge pull request #686 from amnezia-vpn/fix/allowips-config-change
Add AllowedIPs config change
2024-03-12 18:49:09 +00:00
pokamest d443a0063d Merge pull request #681 from amnezia-vpn/bugfix/mobile-auto-focus-disable
First element auto-focus disabled for the mobile platforms
2024-03-12 18:48:33 +00:00
pokamest f0c6edb670 Merge pull request #688 from amnezia-vpn/bugfix/sftp-hostname
bugfix/sftp-hostname
2024-03-12 18:47:42 +00:00
vladimir.kuznetsov 9189b53a0d fixed display of hostName on the sftp settings page 2024-03-12 23:43:24 +05:00
Igor Sorokin fceccaefcc Add AllowedIPs config change 2024-03-12 19:57:45 +03:00
pokamest fbeabf43ca Merge pull request #684 from amnezia-vpn/fix/android-remove-ss 2024-03-12 15:17:55 +00:00
albexk 78c7893f90 Remove shadowsocks libs from Android build 2024-03-12 17:17:38 +03:00
Garegin866 cb9a25006c - Removed additional focus frames for buttons inside text fields.
- For mobile platforms, disabled auto-focus on the first element when navigating on the page.
2024-03-12 00:02:47 +04:00
pokamest 0e87550d85 Merge pull request #672 from amnezia-vpn/translations/fix-translations
Fix translations
2024-03-10 16:18:27 -07:00
pokamest dceb0ab832 Merge pull request #674 from amnezia-vpn/version-bump
Bump Android version code to 47
2024-03-10 04:43:44 -07:00
pokamest a33590476a Merge pull request #677 from artromone/fix/logger
added commit hash in logger
2024-03-08 14:25:19 -08:00
Artem Romanovich deaf618520 added commit hash in logger 2024-03-09 00:21:57 +03:00
pokamest 3d8a56d922 Merge pull request #673 from amnezia-vpn/feature/api-request-debug-output
extended debug output for api request
2024-03-07 04:43:07 -08:00
albexk 36af7cf471 Bump Android version code to 47 2024-03-07 14:27:21 +03:00
vladimir.kuznetsov ebd3449b4a extended debug output for api request 2024-03-07 09:18:25 +03:00
Andrey Zaharow 99182f4a67 Fix translations 2024-03-06 23:04:53 +01:00
pokamest da84ba1a4d Text fixes and some ts updates 2024-03-06 18:34:07 +00:00
pokamest bca68fc185 iOS crash fix 2024-03-06 10:07:49 -08:00
pokamest 59a7265bac Merge pull request #671 from amnezia-vpn/bugfix/fade-on-page-start-repalce
fixed screen fade when switching from PageSetupWizardStart to PageStart
2024-03-06 06:36:07 -08:00
vladimir.kuznetsov 9201ca1e03 fixed screen fade when switching from PageSetupWizardStart to PageStart 2024-03-06 14:22:44 +05:00
dimov96 6b6a76d2cc Replace sftp with scp (#602)
Replace sftp with scp
2024-03-06 01:24:28 +00:00
isamnezia 840c388ab9 Add in-app screenshot preventing (#606)
In-app screenshot preventing fixes
2024-03-06 01:18:19 +00:00
Shehab Ahmed 5b4ec608c8 pushing the Burmese translation file (#669)
Burmese translation
2024-03-05 20:49:30 +00:00
pokamest 79ff1b81e0 Merge pull request #666 from amnezia-vpn/bugfix/Revert_PR_596
ISSUE: In start page, icon is highlighted not correctly when press ESC key
2024-03-05 05:07:05 -08:00
pokamest ea67c01da8 Merge pull request #667 from amnezia-vpn/bugfix/http-replacement
removed the replacement of https by http in apiController
2024-03-04 13:07:06 -08:00
vladimir.kuznetsov 1137e169ea removed the replacement of https by http in apiController 2024-03-04 21:45:04 +03:00
Dan Nguyen 17748cca47 ISSUE: In start page, icon is highlighted not correctly when press ESC key
ROOT CAUSE: The button state is decided by the attribute isServerInfoShow and it was added by commit 68fe20ddf6. The logic to decide whether server info showed is not correct

ACTION: Revert commit 68fe20ddf6
2024-03-04 22:35:34 +07:00
albexk 080e1d98c6 Add Quick Settings tile (#660)
* Add Quick Settings tile

- Add multi-client support to AmneziaVpnService
- Make AmneziaActivity permanently connected to AmneziaVpnService while it is running
- Refactor processing of connection state changes on qt side
- Add VpnState DataStore
- Add check if AmneziaVpnService is running

* Add tile reset when the server is removed from the application
2024-03-04 15:08:55 +00:00
isamnezia ca633ae882 Remove VPN configurations after app reset on iOS (#661) 2024-03-04 12:25:49 +00:00
isamnezia bb7b64fb96 Fix QML glitches and crash on iOS (#658)
Fix QML glitches and crash on iOS and Android
2024-03-03 23:28:10 +00:00
AlexanderGalkov bf901631bf Update Dockerfile (#648)
* Update Dockerfile
* update server scripts for cloak and shadowsocks
* specify the latest cloak and shadowsocks releases in server scripts
2024-03-02 19:45:42 +00:00
Shehab Ahmed 0c0ce54b1f fixed the first-generated QR code is visible while generating another QR code bug (#656) 2024-03-02 19:06:33 +00:00
pokamest ee762c4cef Merge pull request #653 from amnezia-vpn/fix/config-from-tg
Fix adding config from bot (iOS)
2024-02-29 07:05:24 -08:00
pokamest ed9efb5a79 Merge pull request #654 from amnezia-vpn/fix/config-sync-ios
Sync configs and fix bug in NE (iOS)
2024-02-29 03:09:20 -08:00
Igor Sorokin 73eb85f2f4 Sync configs 2024-02-29 13:58:11 +03:00
Nethius cd055cff62 removed the display of servers without containers on PageShare (#609)
* removed the display of servers without containers on PageShare

* removed unused isAnyContainerInstalled() from containers model

* added tab navigation to the share connection drawer

* fixed display of default server without containers on PageShare
2024-02-29 10:22:17 +00:00
Igor Sorokin f8b2cce618 Fix adding config from bot 2024-02-29 08:36:56 +03:00
233 changed files with 12331 additions and 5014 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.4.0.0
project(${PROJECT} VERSION 4.5.0.0
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 46)
set(APP_ANDROID_VERSION_CODE 50)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
+11 -2
View File
@@ -63,10 +63,11 @@ qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
set(CMAKE_AUTORCC ON)
set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
@@ -118,7 +119,9 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
@@ -128,6 +131,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
)
# Mozilla headres
@@ -157,12 +161,15 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
)
# Mozilla sources
@@ -292,6 +299,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
)
@@ -303,6 +311,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
)
endif()
+46 -29
View File
@@ -14,16 +14,19 @@
#include "logger.h"
#include "version.h"
#include "ui/models/installedAppsModel.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h"
#include "core/installedAppsImageProvider.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@@ -81,8 +84,7 @@ void AmneziaApplication::init()
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -97,6 +99,15 @@ void AmneziaApplication::init()
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(),
&AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(),
&AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
@@ -112,8 +123,12 @@ void AmneziaApplication::init()
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
@@ -127,6 +142,11 @@ void AmneziaApplication::init()
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
connect(m_settings.get(), &Settings::screenshotsEnabledChanged,
[](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
m_notificationHandler.reset(NotificationHandler::create(nullptr));
@@ -214,7 +234,8 @@ void AmneziaApplication::registerTypes()
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
//
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
@@ -305,6 +326,9 @@ void AmneziaApplication::initModels()
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
@@ -323,6 +347,9 @@ void AmneziaApplication::initModels()
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
@@ -335,28 +362,30 @@ void AmneziaApplication::initModels()
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile);
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
[this](const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) {
m_serversModel->reloadDefaultServerContainerConfig();
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated();
});
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel,
m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this,
[this](const QString &errorMessage) {
emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
&ConnectionController::toggleConnection, Qt::QueuedConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
&ConnectionController::onTranslationsUpdated);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel,
m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
@@ -368,8 +397,7 @@ void AmneziaApplication::initControllers()
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
m_settings, m_configurator));
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(
@@ -384,20 +412,9 @@ void AmneziaApplication::initControllers()
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_apiController.reset(new ApiController(m_serversModel, m_containersModel));
m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get());
connect(m_apiController.get(), &ApiController::updateStarted, this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); });
connect(m_apiController.get(), &ApiController::errorOccurred, this, [this](const QString &errorMessage) {
if (m_connectionController->isConnectionInProgress()) {
emit m_pageController->showErrorMessage(errorMessage);
}
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(),
&ConnectionController::toggleConnection);
}
+7 -3
View File
@@ -14,7 +14,7 @@
#include "settings.h"
#include "vpnconnection.h"
#include "configurators/vpn_configurator.h"
#include "core/controllers/apiController.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
@@ -24,7 +24,7 @@
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/apiController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
@@ -36,11 +36,13 @@
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@@ -83,7 +85,6 @@ private:
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
@@ -97,11 +98,13 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
@@ -123,6 +126,7 @@ private:
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<ApiController> m_apiController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
};
#endif // AMNEZIA_APPLICATION_H
+21 -3
View File
@@ -24,9 +24,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".AmneziaApplication"
@@ -56,6 +54,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
@@ -146,6 +148,22 @@
</intent-filter>
</service>
<service
android:name=".AmneziaTileService"
android:process=":amneziaTileService"
android:icon="@drawable/ic_amnezia_round"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.qtprovider"
+2 -1
View File
@@ -64,8 +64,9 @@ class Awg : Wireguard() {
val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build {
configWireguard(configData)
configWireguard(configData, configDataJson)
configSplitTunneling(config)
configAppSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) }
configData["Jmax"]?.let { setJmax(it.toInt()) }
+1
View File
@@ -111,4 +111,5 @@ dependencies {
implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
}
+2
View File
@@ -6,6 +6,7 @@ androidx-activity = "1.8.1"
androidx-annotation = "1.7.0"
androidx-camera = "1.3.0"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3"
google-mlkit = "17.2.0"
@@ -18,6 +19,7 @@ androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.r
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
@@ -79,6 +79,7 @@ open class OpenVpn : Protocol() {
}
configPluggableTransport(configBuilder, config)
configBuilder.configSplitTunneling(config)
configBuilder.configAppSplitTunneling(config)
scope.launch {
val status = client.connect()
@@ -64,6 +64,22 @@ abstract class Protocol {
}
}
protected fun ProtocolConfig.Builder.configAppSplitTunneling(config: JSONObject) {
val splitTunnelType = config.optInt("appSplitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelApps = config.getJSONArray("splitTunnelApps")
val appHandlerFunc = when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeApplication
SPLIT_TUNNEL_EXCLUDE -> ::excludeApplication
else -> throw BadConfigException("Unexpected value of the 'appSplitTunnelType' parameter: $splitTunnelType")
}
for (i in 0 until splitTunnelApps.length()) {
appHandlerFunc(splitTunnelApps.getString(i))
}
}
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
vpnBuilder.setSession(VPN_SESSION_NAME)
@@ -101,6 +117,11 @@ abstract class Protocol {
}
}
for (app in config.includedApplications) {
Log.d(TAG, "addAllowedApplication: $app")
vpnBuilder.addAllowedApplication(app)
}
for (app in config.excludedApplications) {
Log.d(TAG, "addDisallowedApplication: $app")
vpnBuilder.addDisallowedApplication(app)
@@ -16,6 +16,7 @@ open class ProtocolConfig protected constructor(
val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val includedApplications: Set<String>,
val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?,
val allowAllAF: Boolean,
@@ -31,6 +32,7 @@ open class ProtocolConfig protected constructor(
builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.includedApplications,
builder.excludedApplications,
builder.httpProxy,
builder.allowAllAF,
@@ -45,6 +47,7 @@ open class ProtocolConfig protected constructor(
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val includedApplications: MutableSet<String> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null
@@ -88,6 +91,9 @@ open class ProtocolConfig protected constructor(
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun includeApplication(application: String) = apply { this.includedApplications += application }
fun includeApplications(applications: Collection<String>) = apply { this.includedApplications += applications }
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@@ -2,9 +2,9 @@ package org.amnezia.vpn.protocol
// keep synchronized with client/platforms/android/android_controller.h ConnectionState
enum class ProtocolState {
DISCONNECTED,
CONNECTED,
CONNECTING,
DISCONNECTED,
DISCONNECTING,
RECONNECTING,
UNKNOWN
@@ -28,6 +28,10 @@ fun Bundle.putStatus(status: Status) {
putInt(STATE_KEY, status.state.ordinal)
}
fun Bundle.putStatus(state: ProtocolState) {
putInt(STATE_KEY, state.ordinal)
}
fun Bundle.getStatus(): Status =
Status.build {
setState(ProtocolState.entries[getInt(STATE_KEY)])
+12
View File
@@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Подключение</string>
<string name="disconnecting">Отключение</string>
<string name="cancel">Отмена</string>
<string name="ok">ОК</string>
<string name="vpnGranted">VPN-подключение разрешено</string>
<string name="vpnDenied">VPN-подключение запрещено</string>
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
<string name="openVpnSettings">Открыть настройки VPN</string>
</resources>
+12
View File
@@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Connecting</string>
<string name="disconnecting">Disconnecting</string>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<string name="vpnGranted">VPN permission granted</string>
<string name="vpnDenied">VPN permission denied</string>
<string name="vpnSetupFailed">VPN setup error</string>
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
<string name="openVpnSettings">Open VPN settings</string>
</resources>
@@ -1,11 +1,13 @@
package org.amnezia.vpn
import android.app.AlertDialog
import android.content.ComponentName
import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.net.VpnService
import android.os.Bundle
@@ -14,6 +16,8 @@ import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.os.Messenger
import android.provider.Settings
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
import android.widget.Toast
import androidx.annotation.MainThread
@@ -21,14 +25,15 @@ import androidx.core.content.ContextCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController
@@ -36,11 +41,11 @@ import org.amnezia.vpn.util.Log
import org.qtproject.qt.android.bindings.QtActivity
private const val TAG = "AmneziaActivity"
const val ACTIVITY_MESSENGER_NAME = "Activity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() {
@@ -58,25 +63,17 @@ class AmneziaActivity : QtActivity() {
val event = msg.extractIpcMessage<ServiceEvent>()
Log.d(TAG, "Handle event: $event")
when (event) {
ServiceEvent.CONNECTED -> {
QtAndroidController.onVpnConnected()
}
ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected()
doUnbindService()
}
ServiceEvent.RECONNECTING -> {
QtAndroidController.onVpnReconnecting()
ServiceEvent.STATUS_CHANGED -> {
msg.data?.getStatus()?.let { (state) ->
Log.d(TAG, "Handle protocol state: $state")
QtAndroidController.onVpnStateChanged(state.ordinal)
}
}
ServiceEvent.STATUS -> {
if (isWaitingStatus) {
isWaitingStatus = false
msg.data?.getStatus()?.let { (state) ->
QtAndroidController.onStatus(state.ordinal)
}
msg.data?.getStatus()?.let { QtAndroidController.onStatus(it) }
}
}
@@ -87,7 +84,7 @@ class AmneziaActivity : QtActivity() {
}
ServiceEvent.ERROR -> {
msg.data?.getString(ERROR_MSG)?.let { error ->
msg.data?.getString(MSG_ERROR)?.let { error ->
Log.e(TAG, "From VpnService: $error")
}
// todo: add error reporting to Qt
@@ -109,14 +106,15 @@ class AmneziaActivity : QtActivity() {
// get a messenger from the service to send actions to the service
vpnServiceMessenger.set(Messenger(service))
// send a messenger to the service to process service events
vpnServiceMessenger.send {
Action.REGISTER_CLIENT.packToMessage().apply {
replyTo = activityMessenger
}
}
vpnServiceMessenger.send(
Action.REGISTER_CLIENT.packToMessage {
putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
},
replyTo = activityMessenger
)
isServiceConnected = true
if (isWaitingStatus) {
vpnServiceMessenger.send(Action.REQUEST_STATUS)
vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger)
}
}
@@ -126,6 +124,7 @@ class AmneziaActivity : QtActivity() {
vpnServiceMessenger.reset()
isWaitingStatus = true
QtAndroidController.onServiceDisconnected()
doBindService()
}
override fun onBindingDied(name: ComponentName?) {
@@ -148,8 +147,11 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService,
messengerName = "VpnService"
"VpnService",
onDeadObjectException = {
doUnbindService()
doBindService()
}
)
intent?.let(::processIntent)
}
@@ -220,13 +222,13 @@ class AmneziaActivity : QtActivity() {
when (resultCode) {
RESULT_OK -> {
Log.d(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
Toast.makeText(this, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() }
}
else -> {
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
showOnVpnPermissionRejectDialog()
checkVpnPermissionCallbacks?.run { onFail() }
}
}
@@ -244,10 +246,9 @@ class AmneziaActivity : QtActivity() {
private fun doBindService() {
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
}
isInBoundState = true
handleBindTimeout()
}
@MainThread
@@ -256,26 +257,14 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Unbind service")
isWaitingStatus = true
QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset()
isServiceConnected = false
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
vpnServiceMessenger.reset()
isInBoundState = false
unbindService(serviceConnection)
}
}
private fun handleBindTimeout() {
mainScope.launch {
if (isWaitingStatus) {
delay(BIND_SERVICE_TIMEOUT)
if (isWaitingStatus && !isServiceConnected) {
Log.d(TAG, "Bind timeout, reset connection status")
isWaitingStatus = false
QtAndroidController.onStatus(ProtocolState.DISCONNECTED.ordinal)
}
}
}
}
/**
* Methods of starting and stopping VpnService
*/
@@ -297,6 +286,17 @@ class AmneziaActivity : QtActivity() {
onSuccess()
}
private fun showOnVpnPermissionRejectDialog() {
AlertDialog.Builder(this)
.setTitle(R.string.vpnSetupFailed)
.setMessage(R.string.vpnSetupFailedMessage)
.setNegativeButton(R.string.ok) { _, _ -> }
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
}
.show()
}
@MainThread
private fun startVpn(vpnConfig: String) {
if (isServiceConnected) {
@@ -312,7 +312,7 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send {
Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig)
putString(MSG_VPN_CONFIG, vpnConfig)
}
}
}
@@ -320,7 +320,7 @@ class AmneziaActivity : QtActivity() {
private fun startVpnService(vpnConfig: String) {
Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig)
putExtra(MSG_VPN_CONFIG, vpnConfig)
}.also {
ContextCompat.startForegroundService(this, it)
}
@@ -369,6 +369,22 @@ class AmneziaActivity : QtActivity() {
}
}
@Suppress("unused")
fun resetLastServer(index: Int) {
Log.v(TAG, "Reset server: $index")
mainScope.launch {
VpnStateStore.store {
if (index == -1 || it.serverIndex == index) {
VpnState.defaultState
} else if (it.serverIndex > index) {
it.copy(serverIndex = it.serverIndex - 1)
} else {
it
}
}
}
}
@Suppress("unused")
fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName")
@@ -438,7 +454,7 @@ class AmneziaActivity : QtActivity() {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
putBoolean(MSG_SAVE_LOGS, enabled)
}
}
}
@@ -455,4 +471,41 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
@Suppress("unused")
fun setScreenshotsEnabled(enabled: Boolean) {
Log.v(TAG, "Set screenshots enabled: $enabled")
mainScope.launch {
val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE
window.setFlags(flag, LayoutParams.FLAG_SECURE)
}
}
@Suppress("unused")
fun minimizeApp() {
Log.v(TAG, "Minimize application")
mainScope.launch {
moveTaskToBack(false)
}
}
@Suppress("unused")
fun getAppList(): String {
Log.v(TAG, "Get app list")
var appList = ""
runBlocking {
mainScope.launch {
withContext(Dispatchers.IO) {
appList = AppListProvider.getAppList(packageManager, packageName)
}
}.join()
}
return appList
}
@Suppress("unused")
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
Log.v(TAG, "Get app icon: $packageName")
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
}
}
@@ -18,6 +18,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
super.onCreate()
Prefs.init(this)
Log.init(this)
VpnStateStore.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel()
}
@@ -0,0 +1,272 @@
package org.amnezia.vpn
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.net.VpnService
import android.os.Build
import android.os.IBinder
import android.os.Messenger
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.core.content.ContextCompat
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.util.Log
private const val TAG = "AmneziaTileService"
private const val DEFAULT_TILE_LABEL = "AmneziaVPN"
class AmneziaTileService : TileService() {
private lateinit var scope: CoroutineScope
private var vpnStateListeningJob: Job? = null
private lateinit var vpnServiceMessenger: IpcMessenger
@Volatile
private var isServiceConnected = false
private var isInBoundState = false
@Volatile
private var isVpnConfigExists = false
private val serviceConnection: ServiceConnection by lazy(NONE) {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "Service ${name?.flattenToString()} was connected")
vpnServiceMessenger.set(Messenger(service))
isServiceConnected = true
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.w(TAG, "Service ${name?.flattenToString()} was unexpectedly disconnected")
isServiceConnected = false
vpnServiceMessenger.reset()
updateVpnState(DISCONNECTED)
}
override fun onBindingDied(name: ComponentName?) {
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
doUnbindService()
doBindService()
}
}
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Create Amnezia Tile Service")
scope = CoroutineScope(SupervisorJob())
vpnServiceMessenger = IpcMessenger(
"VpnService",
onDeadObjectException = ::doUnbindService
)
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia Tile Service")
doUnbindService()
scope.cancel()
super.onDestroy()
}
// Workaround for some bugs
override fun onBind(intent: Intent?): IBinder? =
try {
super.onBind(intent)
} catch (e: Throwable) {
Log.e(TAG, "Failed to bind AmneziaTileService: $e")
null
}
override fun onStartListening() {
super.onStartListening()
Log.d(TAG, "Start listening")
if (AmneziaVpnService.isRunning(applicationContext)) {
Log.d(TAG, "Vpn service is running")
doBindService()
} else {
Log.d(TAG, "Vpn service is not running")
isServiceConnected = false
updateVpnState(DISCONNECTED)
}
vpnStateListeningJob = launchVpnStateListening()
}
override fun onStopListening() {
Log.d(TAG, "Stop listening")
vpnStateListeningJob?.cancel()
vpnStateListeningJob = null
doUnbindService()
super.onStopListening()
}
override fun onClick() {
Log.d(TAG, "onClick")
if (isLocked) {
unlockAndRun { onClickInternal() }
} else {
onClickInternal()
}
}
private fun onClickInternal() {
if (isVpnConfigExists) {
Log.d(TAG, "Change VPN state")
if (qsTile.state == Tile.STATE_INACTIVE) {
Log.d(TAG, "Start VPN")
updateVpnState(CONNECTING)
startVpn()
} else if (qsTile.state == Tile.STATE_ACTIVE) {
Log.d(TAG, "Stop vpn")
updateVpnState(DISCONNECTING)
stopVpn()
}
} else {
Log.d(TAG, "Start Activity")
Intent(this, AmneziaActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
}
}
private fun doBindService() {
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
}
isInBoundState = true
}
private fun doUnbindService() {
if (isInBoundState) {
Log.d(TAG, "Unbind service")
isServiceConnected = false
vpnServiceMessenger.reset()
isInBoundState = false
unbindService(serviceConnection)
}
}
private fun startVpn() {
if (isServiceConnected) {
connectToVpn()
} else {
if (checkPermission()) {
startVpnService()
doBindService()
} else {
updateVpnState(DISCONNECTED)
}
}
}
private fun checkPermission() =
if (VpnService.prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
false
} else {
true
}
private fun startVpnService() =
ContextCompat.startForegroundService(
applicationContext,
Intent(this, AmneziaVpnService::class.java)
)
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
private fun stopVpn() = vpnServiceMessenger.send(Action.DISCONNECT)
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun startActivityAndCollapseCompat(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(
PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
)
} else {
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
}
}
private fun updateVpnState(state: ProtocolState) {
scope.launch {
VpnStateStore.store { it.copy(protocolState = state) }
}
}
private fun launchVpnStateListening() =
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
private fun updateTile(vpnState: VpnState) {
Log.d(TAG, "Update tile: $vpnState")
isVpnConfigExists = vpnState.serverName != null
val tile = qsTile ?: return
tile.apply {
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
when (vpnState.protocolState) {
CONNECTED -> {
state = Tile.STATE_ACTIVE
subtitleCompat = null
}
DISCONNECTED, UNKNOWN -> {
state = Tile.STATE_INACTIVE
subtitleCompat = null
}
CONNECTING, RECONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.connecting)
}
DISCONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.disconnecting)
}
}
updateTile()
}
// double update to fix weird visual glitches
tile.updateTile()
}
private var Tile.subtitleCompat: CharSequence?
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.subtitle = value
}
}
get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return this.subtitle
}
return null
}
}
@@ -1,7 +1,10 @@
package org.amnezia.vpn
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
@@ -16,6 +19,7 @@ import android.os.Process
import androidx.annotation.MainThread
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@@ -26,6 +30,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -39,14 +44,11 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.Status
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.cloak.Cloak
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log
@@ -57,12 +59,16 @@ import org.json.JSONObject
private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val MSG_VPN_CONFIG = "VPN_CONFIG"
const val MSG_ERROR = "ERROR"
const val MSG_SAVE_LOGS = "SAVE_LOGS"
const val MSG_CLIENT_NAME = "CLIENT_NAME"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
private const val NOTIFICATION_ID = 1337
private const val STATISTICS_SENDING_TIMEOUT = 1000L
private const val DISCONNECT_TIMEOUT = 5000L
@@ -76,6 +82,8 @@ class AmneziaVpnService : VpnService() {
private var protocol: Protocol? = null
private val protocolCache = mutableMapOf<String, Protocol>()
private var protocolState = MutableStateFlow(UNKNOWN)
private var serverName: String? = null
private var serverIndex: Int = -1
private val isConnected
get() = protocolState.value == CONNECTED
@@ -89,8 +97,11 @@ class AmneziaVpnService : VpnService() {
private var connectionJob: Job? = null
private var disconnectionJob: Job? = null
private var statisticsSendingJob: Job? = null
private lateinit var clientMessenger: IpcMessenger
private lateinit var networkState: NetworkState
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
private val isActivityConnected
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
protocolState.value = DISCONNECTED
@@ -116,13 +127,22 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Handle action: $action")
when (action) {
Action.REGISTER_CLIENT -> {
clientMessenger.set(msg.replyTo)
val clientName = msg.data.getString(MSG_CLIENT_NAME)
val messenger = IpcMessenger(msg.replyTo, clientName)
clientMessengers[msg.replyTo] = messenger
Log.d(TAG, "Messenger client '$clientName' was registered")
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
}
Action.UNREGISTER_CLIENT -> {
clientMessengers.remove(msg.replyTo)?.let {
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
}
}
Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
connect(msg.data.getString(MSG_VPN_CONFIG))
}
Action.DISCONNECT -> {
@@ -130,17 +150,17 @@ class AmneziaVpnService : VpnService() {
}
Action.REQUEST_STATUS -> {
clientMessenger.send {
ServiceEvent.STATUS.packToMessage {
putStatus(Status.build {
setState(this@AmneziaVpnService.protocolState.value)
})
clientMessengers[msg.replyTo]?.let { clientMessenger ->
clientMessenger.send {
ServiceEvent.STATUS.packToMessage {
putStatus(this@AmneziaVpnService.protocolState.value)
}
}
}
}
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
}
}
}
@@ -189,27 +209,23 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client")
loadServerData()
launchProtocolStateHandler()
networkState = NetworkState(this, ::reconnect)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val isAlwaysOnCompat =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
else intent?.component?.packageName != packageName
val isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE
if (isAlwaysOnCompat) {
if (isAlwaysOn) {
Log.d(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY))
connect()
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.d(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY))
connect()
} else {
Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
}
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
return START_REDELIVER_INTENT
@@ -219,17 +235,16 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onBind by $intent")
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
isServiceBound = true
if (isConnected) launchSendingStatistics()
return vpnServiceMessenger.binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind by $intent")
if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = false
stopSendingStatistics()
clientMessenger.reset()
if (isUnknown || isDisconnected) stopService()
if (clientMessengers.isEmpty()) {
isServiceBound = false
if (isUnknown || isDisconnected) stopService()
}
}
return true
}
@@ -238,7 +253,6 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onRebind by $intent")
if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = true
if (isConnected) launchSendingStatistics()
}
super.onRebind(intent)
}
@@ -278,17 +292,16 @@ class AmneziaVpnService : VpnService() {
*/
private fun launchProtocolStateHandler() {
mainScope.launch {
protocolState.collect { protocolState ->
// drop first default UNKNOWN state
protocolState.drop(1).collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) {
CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED)
networkState.bindNetworkListener()
if (isServiceBound) launchSendingStatistics()
if (isActivityConnected) launchSendingStatistics()
}
DISCONNECTED -> {
clientMessenger.send(ServiceEvent.DISCONNECTED)
networkState.unbindNetworkListener()
stopSendingStatistics()
if (!isServiceBound) stopService()
@@ -300,12 +313,19 @@ class AmneziaVpnService : VpnService() {
}
RECONNECTING -> {
clientMessenger.send(ServiceEvent.RECONNECTING)
stopSendingStatistics()
}
CONNECTING, UNKNOWN -> {}
}
clientMessengers.send {
ServiceEvent.STATUS_CHANGED.packToMessage {
putStatus(protocolState)
}
}
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
}
}
}
@@ -332,7 +352,17 @@ class AmneziaVpnService : VpnService() {
}
@MainThread
private fun connect(vpnConfig: String?) {
private fun connect(vpnConfig: String? = null) {
if (vpnConfig == null) {
connectToVpn(Prefs.load(PREFS_CONFIG_KEY))
} else {
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connectToVpn(vpnConfig)
}
}
@MainThread
private fun connectToVpn(vpnConfig: String) {
if (isConnected || protocolState.value == CONNECTING) return
Log.d(TAG, "Start VPN connection")
@@ -340,6 +370,7 @@ class AmneziaVpnService : VpnService() {
protocolState.value = CONNECTING
val config = parseConfigToJson(vpnConfig)
saveServerData(config)
if (config == null) {
onError("Invalid VPN config")
protocolState.value = DISCONNECTED
@@ -417,24 +448,38 @@ class AmneziaVpnService : VpnService() {
private fun onError(msg: String) {
Log.e(TAG, msg)
mainScope.launch {
clientMessenger.send {
clientMessengers.send {
ServiceEvent.ERROR.packToMessage {
putString(ERROR_MSG, msg)
putString(MSG_ERROR, msg)
}
}
}
}
private fun parseConfigToJson(vpnConfig: String?): JSONObject? =
try {
vpnConfig?.let {
JSONObject(it)
}
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
private fun parseConfigToJson(vpnConfig: String): JSONObject? =
if (vpnConfig.isBlank()) {
null
} else {
try {
JSONObject(vpnConfig)
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
null
}
}
private fun saveServerData(config: JSONObject?) {
serverName = config?.opt("description") as String?
serverIndex = config?.opt("serverIndex") as Int? ?: -1
Prefs.save(PREFS_SERVER_NAME, serverName)
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
}
private fun loadServerData() {
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
}
private fun checkPermission(): Boolean =
if (prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply {
@@ -446,4 +491,12 @@ class AmneziaVpnService : VpnService() {
} else {
true
}
companion object {
fun isRunning(context: Context): Boolean =
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
.runningAppProcesses.any {
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
}
}
}
@@ -0,0 +1,73 @@
import android.Manifest.permission.INTERNET
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import androidx.core.graphics.drawable.toBitmapOrNull
import org.amnezia.vpn.util.Log
import org.json.JSONArray
import org.json.JSONObject
private const val TAG = "AppListProvider"
object AppListProvider {
fun getAppList(pm: PackageManager, selfPackageName: String): String {
val jsonArray = JSONArray()
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
.filter { it.packageName != selfPackageName }
.map { App(it, pm) }
.sortedWith(App::compareTo)
.map(App::toJson)
.forEach(jsonArray::put)
return jsonArray.toString()
}
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
val icon = try {
pm.getApplicationIcon(packageName)
} catch (e: NameNotFoundException) {
Log.e(TAG, "Package $packageName was not found: $e")
pm.defaultActivityIcon
}
val w: Int = if (width > 0) width else icon.intrinsicWidth
val h: Int = if (height > 0) height else icon.intrinsicHeight
return icon.toBitmapOrNull(w, h, ARGB_8888)
?: Bitmap.createBitmap(w, h, ARGB_8888)
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {
val r = other.isLaunchable.compareTo(isLaunchable)
if (r != 0) return r
if (name != other.name) {
return when {
name == null -> 1
other.name == null -> -1
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
}
}
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
}
fun toJson(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("package", packageName)
jsonObject.put("name", name)
jsonObject.put("icon", icon)
jsonObject.put("launchable", isLaunchable)
return jsonObject
}
}
@@ -20,9 +20,7 @@ sealed interface IpcMessage {
}
enum class ServiceEvent : IpcMessage {
CONNECTED,
DISCONNECTED,
RECONNECTING,
STATUS_CHANGED,
STATUS,
STATISTICS_UPDATE,
ERROR
@@ -30,6 +28,7 @@ enum class ServiceEvent : IpcMessage {
enum class Action : IpcMessage {
REGISTER_CLIENT,
UNREGISTER_CLIENT,
CONNECT,
DISCONNECT,
REQUEST_STATUS,
@@ -9,11 +9,21 @@ import org.amnezia.vpn.util.Log
private const val TAG = "IpcMessenger"
class IpcMessenger(
messengerName: String? = null,
private val onDeadObjectException: () -> Unit = {},
private val onRemoteException: () -> Unit = {},
private val messengerName: String = "Unknown"
private val onRemoteException: () -> Unit = {}
) {
private var messenger: Messenger? = null
val name = messengerName ?: "Unknown"
constructor(
messenger: Messenger,
messengerName: String? = null,
onDeadObjectException: () -> Unit = {},
onRemoteException: () -> Unit = {}
) : this(messengerName, onDeadObjectException, onRemoteException) {
this.messenger = messenger
}
fun set(messenger: Messenger) {
this.messenger = messenger
@@ -25,19 +35,29 @@ class IpcMessenger(
fun send(msg: () -> Message) = messenger?.sendMsg(msg())
fun send(msg: Message, replyTo: Messenger) = messenger?.sendMsg(msg.apply { this.replyTo = replyTo })
fun <T> send(msg: T)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
fun <T> send(msg: T, replyTo: Messenger)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage().apply { this.replyTo = replyTo })
private fun Messenger.sendMsg(msg: Message) {
try {
send(msg)
} catch (e: DeadObjectException) {
Log.w(TAG, "$messengerName messenger is dead")
Log.w(TAG, "$name messenger is dead")
messenger = null
onDeadObjectException()
} catch (e: RemoteException) {
Log.w(TAG, "Sending a message to the $messengerName messenger failed: ${e.message}")
Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}")
onRemoteException()
}
}
}
fun Map<Messenger, IpcMessenger>.send(msg: () -> Message) = this.values.forEach { it.send(msg) }
fun <T> Map<Messenger, IpcMessenger>.send(msg: T)
where T : Enum<T>, T : IpcMessage = this.values.forEach { it.send(msg) }
@@ -1,12 +1,16 @@
package org.amnezia.vpn
import android.app.AlertDialog
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.VpnService
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResult
@@ -52,19 +56,43 @@ class VpnRequestActivity : ComponentActivity() {
}
private fun checkRequestResult(result: ActivityResult) {
when (result.resultCode) {
RESULT_OK -> onPermissionGranted()
else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
when (val resultCode = result.resultCode) {
RESULT_OK -> {
onPermissionGranted()
finish()
}
else -> {
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
showOnVpnPermissionRejectDialog()
}
}
finish()
}
private fun onPermissionGranted() {
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
Intent(applicationContext, AmneziaVpnService::class.java).apply {
putExtra(AFTER_PERMISSION_CHECK, true)
}.also {
ContextCompat.startForegroundService(this, it)
}
}
private fun showOnVpnPermissionRejectDialog() {
AlertDialog.Builder(this, getDialogTheme())
.setTitle(R.string.vpnSetupFailed)
.setMessage(R.string.vpnSetupFailedMessage)
.setNegativeButton(R.string.ok) { _, _ -> }
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
}
.setOnDismissListener { finish() }
.show()
}
private fun getDialogTheme(): Int =
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
android.R.style.Theme_DeviceDefault_Dialog_Alert
else
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert
}
@@ -0,0 +1,75 @@
package org.amnezia.vpn
import android.app.Application
import androidx.datastore.core.MultiProcessDataStoreFactory
import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.OutputStream
import java.io.Serializable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.util.Log
private const val TAG = "VpnState"
private const val STORE_FILE_NAME = "vpnState"
data class VpnState(
val protocolState: ProtocolState,
val serverName: String? = null,
val serverIndex: Int = -1
) : Serializable {
companion object {
private const val serialVersionUID: Long = -1760654961004181606
val defaultState: VpnState = VpnState(DISCONNECTED)
}
}
object VpnStateStore {
private lateinit var app: Application
private val dataStore = MultiProcessDataStoreFactory.create(
serializer = VpnStateSerializer(),
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
)
fun init(app: Application) {
Log.v(TAG, "Init VpnStateStore")
this.app = app
}
fun dataFlow(): Flow<VpnState> = dataStore.data
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
try {
dataStore.updateData(f)
} catch (e : Exception) {
Log.e(TAG, "Failed to store VpnState: $e")
}
}
}
private class VpnStateSerializer : Serializer<VpnState> {
override val defaultValue: VpnState = VpnState.defaultState
override suspend fun readFrom(input: InputStream): VpnState {
return withContext(Dispatchers.IO) {
ObjectInputStream(input).use {
it.readObject() as VpnState
}
}
}
override suspend fun writeTo(t: VpnState, output: OutputStream) {
withContext(Dispatchers.IO) {
ObjectOutputStream(output).use {
it.writeObject(t)
}
}
}
}
@@ -1,18 +1,23 @@
package org.amnezia.vpn.qt
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.Status
/**
* JNI functions of the AndroidController class from android_controller.cpp,
* called by events in the Android part of the client
*/
object QtAndroidController {
fun onStatus(status: Status) = onStatus(status.state)
fun onStatus(protocolState: ProtocolState) = onStatus(protocolState.ordinal)
external fun onStatus(stateCode: Int)
external fun onServiceDisconnected()
external fun onServiceError()
external fun onVpnPermissionRejected()
external fun onVpnConnected()
external fun onVpnDisconnected()
external fun onVpnReconnecting()
external fun onVpnStateChanged(stateCode: Int)
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String)
@@ -1,10 +0,0 @@
package org.amnezia.vpn.protocol.wireguard
object GoBackend {
external fun wgGetConfig(handle: Int): String?
external fun wgGetSocketV4(handle: Int): Int
external fun wgGetSocketV6(handle: Int): Int
external fun wgTurnOff(handle: Int)
external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun wgVersion(): String
}
@@ -0,0 +1,10 @@
package org.amnezia.awg
object GoBackend {
external fun awgGetConfig(handle: Int): String?
external fun awgGetSocketV4(handle: Int): Int
external fun awgGetSocketV6(handle: Int): Int
external fun awgTurnOff(handle: Int)
external fun awgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun awgVersion(): String
}
@@ -4,6 +4,7 @@ import android.content.Context
import android.net.VpnService.Builder
import java.util.TreeMap
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.awg.GoBackend
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
@@ -61,7 +62,7 @@ open class Wireguard : Protocol() {
override val statistics: Statistics
get() {
if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS
val config = GoBackend.wgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
val config = GoBackend.awgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
return Statistics.build {
var optsCount = 0
config.splitToSequence("\n").forEach { line ->
@@ -92,12 +93,13 @@ open class Wireguard : Protocol() {
val configDataJson = config.getJSONObject("wireguard_config_data")
val configData = parseConfigData(configDataJson.getString("config"))
return WireguardConfig.build {
configWireguard(configData)
configWireguard(configData, configDataJson)
configSplitTunneling(config)
configAppSplitTunneling(config)
}
}
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
@@ -118,7 +120,14 @@ open class Wireguard : Protocol() {
if (routes.any { it !in defRoutes }) disableSplitTunneling()
addRoutes(routes)
configData["MTU"]?.let { setMtu(it.toInt()) }
configDataJson.optString("mtu").let { mtu ->
if (mtu.isNotEmpty()) {
setMtu(mtu.toInt())
} else {
configData["MTU"]?.let { setMtu(it.toInt()) }
}
}
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
@@ -149,8 +158,8 @@ open class Wireguard : Protocol() {
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}")
tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
Log.i(TAG, "awg-go backend ${GoBackend.awgVersion()}")
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
}
if (tunnelHandle < 0) {
@@ -158,8 +167,8 @@ open class Wireguard : Protocol() {
throw VpnStartException("Wireguard tunnel creation error")
}
if (!protect(GoBackend.wgGetSocketV4(tunnelHandle)) || !protect(GoBackend.wgGetSocketV6(tunnelHandle))) {
GoBackend.wgTurnOff(tunnelHandle)
if (!protect(GoBackend.awgGetSocketV4(tunnelHandle)) || !protect(GoBackend.awgGetSocketV6(tunnelHandle))) {
GoBackend.awgTurnOff(tunnelHandle)
tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
}
@@ -172,7 +181,7 @@ open class Wireguard : Protocol() {
}
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.wgTurnOff(handleToClose)
GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED
}
+5 -6
View File
@@ -20,7 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate)
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
@@ -30,6 +30,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
)
set(SOURCES ${SOURCES}
@@ -38,20 +39,18 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
)
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libredsocks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libsslocal.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libtun2socks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libssl_3.so
)
endforeach()
+1
View File
@@ -107,6 +107,7 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
)
target_sources(${PROJECT} PRIVATE
+5 -3
View File
@@ -10,10 +10,10 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *pa
{
}
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString();
@@ -41,6 +41,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().
value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson();
}
+2 -2
View File
@@ -11,8 +11,8 @@ class AwgConfigurator : public WireguardConfigurator
public:
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
};
#endif // AWGCONFIGURATOR_H
+9 -8
View File
@@ -13,22 +13,24 @@ CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject
}
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckPublicKeyPath, &e);
amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
cloakPublicKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
cloakBypassUid.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
if (errorCode != ErrorCode::NoError) {
return "";
}
@@ -48,6 +50,5 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
serverController.genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg;
}
+3 -3
View File
@@ -7,14 +7,14 @@
using namespace amnezia;
class CloakConfigurator : ConfiguratorBase
class CloakConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
};
#endif // CLOAK_CONFIGURATOR_H
+21 -3
View File
@@ -1,8 +1,26 @@
#include "configurator_base.h"
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent)
: QObject{parent},
m_settings(settings)
: QObject { parent }, m_settings(settings)
{
}
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
{
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
}
+11 -2
View File
@@ -3,10 +3,9 @@
#include <QObject>
class Settings;
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "settings.h"
class ConfiguratorBase : public QObject
{
@@ -14,7 +13,17 @@ class ConfiguratorBase : public QObject
public:
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode) = 0;
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
protected:
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
std::shared_ptr<Settings> m_settings;
};
+8 -8
View File
@@ -20,7 +20,7 @@ Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject
}
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode)
DockerContainer container, ErrorCode errorCode)
{
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
@@ -40,17 +40,17 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
.arg(connData.clientId);
ServerController serverController(m_settings);
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
errorCode = serverController.runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
.arg(connData.password)
.arg(connData.clientId)
.arg(certFileName);
e = serverController.runContainerScript(credentials, container, scriptExportCert);
errorCode = serverController.runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert =
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
@@ -58,13 +58,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData;
}
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
Q_UNUSED(containerConfig)
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode && *errorCode) {
if (errorCode != ErrorCode::NoError) {
return "";
}
+4 -4
View File
@@ -7,7 +7,7 @@
#include "configurator_base.h"
#include "core/defs.h"
class Ikev2Configurator : ConfiguratorBase
class Ikev2Configurator : public ConfiguratorBase
{
Q_OBJECT
public:
@@ -21,15 +21,15 @@ public:
QString host; // host ip
};
QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
DockerContainer container, ErrorCode errorCode);
};
#endif // IKEV2_CONFIGURATOR_H
+29 -31
View File
@@ -14,9 +14,9 @@
#endif
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "core/controllers/serverController.h"
#include "settings.h"
#include "utilities.h"
@@ -31,59 +31,51 @@ OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QOb
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container,
ErrorCode *errorCode)
ErrorCode errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
if (errorCode)
*errorCode = ErrorCode::OpenSslFailed;
errorCode = ErrorCode::OpenSslFailed;
return connData;
}
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
ServerController serverController(m_settings);
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (e) {
if (errorCode)
*errorCode = e;
errorCode = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (errorCode != ErrorCode::NoError) {
return connData;
}
e = signCert(container, credentials, connData.clientId);
if (e) {
if (errorCode)
*errorCode = e;
errorCode = signCert(container, credentials, connData.clientId);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.caCert = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::openvpn::caCertPath, &e);
amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = serverController.getTextFileFromContainer(
container, credentials,
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
if (e) {
if (errorCode)
*errorCode = e;
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.taKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::openvpn::taKeyPath, &e);
amnezia::protocols::openvpn::taKeyPath, errorCode);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode)
*errorCode = ErrorCode::SshSftpFailureError;
errorCode = ErrorCode::SshScpFailureError;
}
return connData;
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
ServerController serverController(m_settings);
QString config =
@@ -91,7 +83,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
serverController.genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode && *errorCode) {
if (errorCode != ErrorCode::NoError) {
return "";
}
@@ -113,17 +105,20 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
QJsonObject jConfig;
jConfig[config_key::config] = config;
clientId = connData.clientId;
jConfig[config_key::clientId] = connData.clientId;
return QJsonDocument(jConfig).toJson();
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
@@ -138,9 +133,9 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig,
// no redirect-gateway
}
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID
#ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif
#endif
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
@@ -164,9 +159,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig,
return QJsonDocument(json).toJson();
}
QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
QRegularExpression regex("redirect-gateway.*");
+17 -17
View File
@@ -7,37 +7,37 @@
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator : ConfiguratorBase
class OpenVpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
struct ConnectionData {
struct ConnectionData
{
QString clientId;
QString request; // certificate request
QString privKey; // client private key
QString request; // certificate request
QString privKey; // client private key
QString clientCert; // client signed certificate
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
};
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData createCertRequest();
private:
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode *errorCode = nullptr);
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode errorCode);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
};
#endif // OPENVPN_CONFIGURATOR_H
@@ -13,18 +13,16 @@ ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> setti
}
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
QString ssKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::shadowsocks::ssKeyPath, &e);
amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
ssKey.replace("\n", "");
if (e) {
if (errorCode) *errorCode = e;
if (errorCode != ErrorCode::NoError) {
return "";
}
@@ -6,14 +6,14 @@
#include "configurator_base.h"
#include "core/defs.h"
class ShadowSocksConfigurator : ConfiguratorBase
class ShadowSocksConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
};
#endif // SHADOWSOCKS_CONFIGURATOR_H
-127
View File
@@ -1,127 +0,0 @@
#include "vpn_configurator.h"
#include "cloak_configurator.h"
#include "ikev2_configurator.h"
#include "openvpn_configurator.h"
#include "shadowsocks_configurator.h"
#include "ssh_configurator.h"
#include "wireguard_configurator.h"
#include "awg_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "settings.h"
#include "utilities.h"
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
: ConfiguratorBase(settings, parent)
{
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
cloakConfigurator = std::shared_ptr<CloakConfigurator>(new CloakConfigurator(settings, this));
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, false, this));
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
{
switch (proto) {
case Proto::OpenVpn:
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::ShadowSocks:
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
case Proto::WireGuard:
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Awg:
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
default: return "";
}
}
QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
{
QPair<QString, QString> dns;
bool useAmneziaDns = m_settings->useAmneziaDns();
const QJsonObject &server = m_settings->server(serverIndex);
dns.first = server.value(config_key::dns1).toString();
dns.second = server.value(config_key::dns2).toString();
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
dns.first = protocols::dns::amneziaDnsIp;
} else
dns.first = m_settings->primaryDns();
}
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
dns.second = m_settings->secondaryDns();
}
qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second;
return dns;
}
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
auto dns = getDnsForConfig(serverIndex);
config.replace("$PRIMARY_DNS", dns.first);
config.replace("$SECONDARY_DNS", dns.second);
return config;
}
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
}
return config;
}
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithExportSettings(config);
}
return config;
}
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
if (container == DockerContainer::TorWebSite) {
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
qDebug() << "amnezia-tor onions" << stdOut;
QString onion = stdOut;
onion.replace("\n", "");
protocol.insert(config_key::site, onion);
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
}
}
-52
View File
@@ -1,52 +0,0 @@
#ifndef VPN_CONFIGURATOR_H
#define VPN_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator;
class ShadowSocksConfigurator;
class CloakConfigurator;
class WireguardConfigurator;
class Ikev2Configurator;
class SshConfigurator;
class AwgConfigurator;
// Retrieve connection settings from server
class VpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, QString &clientId,
ErrorCode *errorCode = nullptr);
QPair<QString, QString> getDnsForConfig(int serverIndex);
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
// workaround for containers which is not support normal configuration
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut);
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
std::shared_ptr<CloakConfigurator> cloakConfigurator;
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
std::shared_ptr<SshConfigurator> sshConfigurator;
std::shared_ptr<AwgConfigurator> awgConfigurator;
signals:
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
void clientModelUpdated();
};
#endif // VPN_CONFIGURATOR_H
+37 -50
View File
@@ -13,23 +13,22 @@
#include <openssl/x509.h>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "core/controllers/serverController.h"
#include "settings.h"
#include "utilities.h"
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent)
: ConfiguratorBase(settings, parent), m_isAwg(isAwg)
{
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath
: amnezia::protocols::wireguard::serverConfigPath;
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath
: amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath
: amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template
: ProtocolScriptType::wireguard_template;
m_serverConfigPath =
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
m_serverPublicKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
@@ -69,19 +68,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig,
ErrorCode *errorCode)
ErrorCode errorCode)
{
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName;
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
if (errorCode)
*errorCode = ErrorCode::InternalError;
errorCode = ErrorCode::InternalError;
return connData;
}
ErrorCode e = ErrorCode::NoError;
ServerController serverController(m_settings);
// Get list of already created clients (only IP addresses)
@@ -94,9 +91,8 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return ErrorCode::NoError;
};
e = serverController.runContainerScript(credentials, container, script, cbReadStdOut);
if (errorCode && e) {
*errorCode = e;
errorCode = serverController.runContainerScript(credentials, container, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return connData;
}
@@ -110,8 +106,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} else {
int next = ips.last().split(".").last().toInt() + 1;
if (next > 254) {
if (errorCode)
*errorCode = ErrorCode::AddressPoolError;
errorCode = ErrorCode::AddressPoolError;
return connData;
}
nextIpNumber = QString::number(next);
@@ -123,8 +118,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
{
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) {
if (errorCode)
*errorCode = ErrorCode::AddressPoolError;
errorCode = ErrorCode::AddressPoolError;
return connData;
}
l.removeLast();
@@ -134,20 +128,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
// Get keys
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e);
connData.serverPubKey =
serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", "");
if (e) {
if (errorCode)
*errorCode = e;
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e);
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey.replace("\n", "");
if (e) {
if (errorCode)
*errorCode = e;
if (errorCode != ErrorCode::NoError) {
return connData;
}
@@ -158,26 +149,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::SftpOverwriteMode::SftpAppendToExisting);
errorCode = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (e) {
if (errorCode)
*errorCode = e;
if (errorCode != ErrorCode::NoError) {
return connData;
}
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
e = serverController.runScript(
errorCode = serverController.runScript(
credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
return connData;
}
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode)
{
ServerController serverController(m_settings);
QString scriptData = amnezia::scriptData(m_configTemplate, container);
@@ -185,7 +174,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
scriptData, serverController.genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode && *errorCode) {
if (errorCode != ErrorCode::NoError) {
return "";
}
@@ -194,6 +183,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig;
jConfig[config_key::config] = config;
@@ -204,28 +194,25 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::client_pub_key] = connData.clientPubKey;
jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
clientId = connData.clientPubKey;
jConfig[config_key::clientId] = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();
}
QString WireguardConfigurator::processConfigWithLocalSettings(QString config)
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
{
// TODO replace DNS if it already set
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject jConfig;
jConfig[config_key::config] = config;
return QJsonDocument(jConfig).toJson();
return protocolConfigString;
}
QString WireguardConfigurator::processConfigWithExportSettings(QString config)
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
const bool isApiConfig, QString &protocolConfigString)
{
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
processConfigWithDnsSettings(dns, protocolConfigString);
return config;
return protocolConfigString;
}
@@ -25,18 +25,20 @@ public:
QString port;
};
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode);
QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData genClientKeys();
private:
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, ErrorCode errorCode);
bool m_isAwg;
QString m_serverConfigPath;
QString m_serverPublicKeyPath;
@@ -0,0 +1,42 @@
#include "xray_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, QObject *parent) : ConfiguratorBase(settings, parent)
{
}
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode errorCode)
{
ServerController serverController(m_settings);
QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
serverController.genVarsForScript(credentials, container, containerConfig));
QString xrayPublicKey =
serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
xrayPublicKey.replace("\n", "");
QString xrayUuid = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
xrayUuid.replace("\n", "");
QString xrayShortId = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
xrayShortId.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
config.replace("$XRAY_CLIENT_ID", xrayUuid);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
return config;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef XRAY_CONFIGURATOR_H
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class XrayConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
XrayConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode errorCode);
};
#endif // XRAY_CONFIGURATOR_H
+36 -2
View File
@@ -1,5 +1,8 @@
#include "containers_defs.h"
#include "QJsonObject"
#include "QJsonDocument"
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
{
QDebugStateSaver saver(debug);
@@ -58,6 +61,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
case DockerContainer::Xray: return { Proto::Xray };
case DockerContainer::Dns: return { Proto::Dns };
case DockerContainer::Sftp: return { Proto::Sftp };
@@ -85,6 +90,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
@@ -111,6 +117,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
"but very resistant to blockages. "
"Recommended for regions with high levels of censorship.") },
{ DockerContainer::Xray,
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
{ DockerContainer::Ipsec,
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
"signal loss. It has native support on the latest versions of Android and iOS.") },
@@ -199,6 +208,17 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimum number of settings\n"
"* Not recognised by DPI analysis systems, resistant to blocking\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Xray,
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
"thus presenting an authentic TLS certificate and data. \n"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
},
{ DockerContainer::Ipsec,
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
@@ -213,7 +233,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("DNS Service") },
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
{ DockerContainer::Sftp,
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
"You will be able to access it using\n FileZilla or other SFTP clients, "
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
};
}
@@ -231,6 +255,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2;
case DockerContainer::TorWebSite: return Proto::TorWebSite;
@@ -274,7 +299,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
#elif defined(Q_OS_LINUX)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Ipsec: return false;
default: return true;
}
@@ -342,3 +366,13 @@ bool ContainerProps::isShareable(DockerContainer container)
default: return true;
}
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
.toObject()
.value(config_key::last_config)
.toString();
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
}
+3
View File
@@ -22,6 +22,7 @@ namespace amnezia
Cloak,
ShadowSocks,
Ipsec,
Xray,
// non-vpn
TorWebSite,
@@ -67,6 +68,8 @@ namespace amnezia
static int easySetupOrder(amnezia::DockerContainer container);
static bool isShareable(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
};
static void declareQmlContainerEnum()
@@ -5,9 +5,8 @@
#include <QNetworkReply>
#include <QtConcurrent>
#include "configurators/openvpn_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "core/errorstrings.h"
#include "configurators/wireguard_configurator.h"
namespace
{
@@ -24,9 +23,7 @@ namespace
}
}
ApiController::ApiController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel)
ApiController::ApiController(QObject *parent) : QObject(parent)
{
}
@@ -67,21 +64,14 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont
return obj;
}
void ApiController::updateServerConfigFromApi()
ErrorCode ApiController::updateServerConfigFromApi(QJsonObject &serverConfig)
{
QtConcurrent::run([this]() {
if (m_isConfigUpdateStarted) {
emit updateFinished(false);
return;
}
QFutureWatcher<ErrorCode> watcher;
auto serverConfig = m_serversModel->getDefaultServerConfig();
QFuture<ErrorCode> future = QtConcurrent::run([this, &serverConfig]() {
auto containerConfig = serverConfig.value(config_key::containers).toArray();
if (serverConfig.value(config_key::configVersion).toInt() && containerConfig.isEmpty()) {
emit updateStarted();
m_isConfigUpdateStarted = true;
if (serverConfig.value(config_key::configVersion).toInt()) {
QNetworkAccessManager manager;
QNetworkRequest request;
@@ -90,7 +80,7 @@ void ApiController::updateServerConfigFromApi()
request.setRawHeader("Authorization",
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
request.setUrl(endpoint.replace("https", "http")); // todo remove
request.setUrl(endpoint);
QString protocol = serverConfig.value(configKey::protocol).toString();
@@ -114,9 +104,7 @@ void ApiController::updateServerConfigFromApi()
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
if (ba.isEmpty()) {
emit errorOccurred(errorString(ApiConfigDownloadError));
m_isConfigUpdateStarted = false;
return;
return ErrorCode::ApiConfigDownloadError;
}
QByteArray ba_uncompressed = qUncompress(ba);
@@ -136,32 +124,22 @@ void ApiController::updateServerConfigFromApi()
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
serverConfig.insert(config_key::defaultContainer, defaultContainer);
m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex());
} else {
QString err = reply->errorString();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
emit errorOccurred(errorString(ApiConfigDownloadError));
m_isConfigUpdateStarted = false;
return;
return ErrorCode::ApiConfigDownloadError;
}
}
emit updateFinished(m_isConfigUpdateStarted);
m_isConfigUpdateStarted = false;
return;
return ErrorCode::NoError;
});
}
void ApiController::clearApiConfig()
{
auto serverConfig = m_serversModel->getDefaultServerConfig();
serverConfig.remove(config_key::dns1);
serverConfig.remove(config_key::dns2);
serverConfig.remove(config_key::containers);
serverConfig.remove(config_key::hostName);
serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None));
m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex());
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
return watcher.result();
}
@@ -4,26 +4,16 @@
#include <QObject>
#include "configurators/openvpn_configurator.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
class ApiController : public QObject
{
Q_OBJECT
public:
explicit ApiController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel, QObject *parent = nullptr);
explicit ApiController(QObject *parent = nullptr);
public slots:
void updateServerConfigFromApi();
void clearApiConfig();
signals:
void updateStarted();
void updateFinished(bool isConfigUpdateStarted);
void errorOccurred(const QString &errorMessage);
ErrorCode updateServerConfigFromApi(QJsonObject &serverConfig);
private:
struct ApiPayloadData {
@@ -36,11 +26,6 @@ private:
ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
bool m_isConfigUpdateStarted = false;
};
#endif // APICONTROLLER_H
+46 -167
View File
@@ -26,10 +26,10 @@
#include "logger.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "core/networkUtilities.h"
#include "settings.h"
#include "utilities.h"
#include <configurators/vpn_configurator.h>
#include "vpnConfigurationController.h"
namespace
{
@@ -118,7 +118,7 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode)
libssh::ScpOverwriteMode overwriteMode)
{
ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
@@ -139,7 +139,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e)
return e;
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)),
@@ -147,7 +147,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e)
return e;
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)),
@@ -178,11 +178,10 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
}
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode)
const QString &path, ErrorCode errorCode)
{
if (errorCode)
*errorCode = ErrorCode::NoError;
errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"")
.arg(ContainerProps::containerToString(container))
@@ -194,12 +193,12 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
return ErrorCode::NoError;
};
*errorCode = runScript(credentials, script, cbReadStdOut);
errorCode = runScript(credentials, script, cbReadStdOut);
return QByteArray::fromHex(stdOut.toUtf8());
}
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
const QString &remotePath, libssh::ScpOverwriteMode overwriteMode)
{
auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) {
@@ -211,7 +210,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data);
localFile.close();
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
if (error != ErrorCode::NoError) {
return error;
@@ -359,7 +358,33 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::Awg) {
return true;
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
!= newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize))
|| (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)
!= newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize))
|| (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)
!= newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize))
|| (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)
!= newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize))
|| (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)
!= newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader))
|| (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
return true;
}
if (container == DockerContainer::WireGuard){
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
return true;
}
return false;
@@ -418,9 +443,6 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
stdOut += data + "\n";
return ErrorCode::NoError;
};
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
e = runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
@@ -440,9 +462,6 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
stdOut += data + "\n";
return ErrorCode::NoError;
};
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
@@ -477,7 +496,7 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut);
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
return e;
}
@@ -511,6 +530,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
Vars vars;
@@ -568,6 +588,10 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
// Xray vars
vars.append({ { "$XRAY_SITE_NAME",
xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
// Wireguard vars
vars.append(
{ { "$WIREGUARD_SUBNET_IP",
@@ -626,7 +650,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
QString serverIp = Utils::getIPAddress(credentials.hostName);
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
if (!serverIp.isEmpty()) {
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
} else {
@@ -636,7 +660,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
return vars;
}
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -648,11 +672,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
return ErrorCode::NoError;
};
ErrorCode e =
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
if (errorCode)
*errorCode = e;
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
return stdOut;
}
@@ -813,147 +833,6 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
return future.result();
}
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
QMap<DockerContainer, QJsonObject> &installedContainers)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto containersInfo = stdOut.split("\n");
for (auto &containerInfo : containersInfo) {
if (containerInfo.isEmpty()) {
continue;
}
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
if (containerAndPortMatch.hasMatch()) {
QString name = containerAndPortMatch.captured(1);
QString port = containerAndPortMatch.captured(2);
QString transportProto = containerAndPortMatch.captured(3);
DockerContainer container = ContainerProps::containerFromString(name);
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject containerConfig;
if (protocol == mainProto) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
if (protocol == Proto::Awg) {
QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
} else if (protocol == Proto::Sftp) {
stdOut.clear();
script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name);
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto sftpInfo = stdOut.split(":");
if (sftpInfo.size() < 2) {
logger.error() << "Key parameters for the sftp container are missing";
continue;
}
auto userName = sftpInfo.at(0);
userName = userName.remove(0, 1);
auto password = sftpInfo.at(1);
containerConfig.insert(config_key::userName, userName);
containerConfig.insert(config_key::password, password);
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
installedContainers.insert(container, config);
}
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
if (torOrDnsRegMatch.hasMatch()) {
QString name = torOrDnsRegMatch.captured(1);
QString port = torOrDnsRegMatch.captured(2);
QString transportProto = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerProps::containerFromString(name);
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject containerConfig;
if (protocol == mainProto) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
if (protocol == Proto::TorWebSite) {
stdOut.clear();
script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name);
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (stdOut.isEmpty()) {
logger.error() << "Key parameters for the tor container are missing";
continue;
}
QString onion = stdOut;
onion.replace("\n", "");
containerConfig.insert(config_key::site, onion);
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
installedContainers.insert(container, config);
}
}
return ErrorCode::NoError;
}
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
const std::function<QString()> &callback)
{
+4 -7
View File
@@ -30,17 +30,14 @@ public:
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &oldConfig, QJsonObject &newConfig);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
QMap<DockerContainer, QJsonObject> &installedContainers);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject());
ErrorCode uploadTextFileToContainer(
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode = nullptr);
const QString &path, ErrorCode errorCode);
QString replaceVars(const QString &script, const Vars &vars);
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
@@ -55,7 +52,7 @@ public:
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode);
void cancelInstallation();
@@ -80,7 +77,7 @@ private:
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
@@ -0,0 +1,137 @@
#include "vpnConfigurationController.h"
#include "configurators/awg_configurator.h"
#include "configurators/cloak_configurator.h"
#include "configurators/ikev2_configurator.h"
#include "configurators/openvpn_configurator.h"
#include "configurators/shadowsocks_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "configurators/xray_configurator.h"
VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject { parent }, m_settings(settings)
{
}
QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator(const Proto protocol)
{
switch (protocol) {
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(m_settings));
case Proto::ShadowSocks: return QScopedPointer<ConfiguratorBase>(new ShadowSocksConfigurator(m_settings));
case Proto::Cloak: return QScopedPointer<ConfiguratorBase>(new CloakConfigurator(m_settings));
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(m_settings, false));
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings));
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings));
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings));
default: return QScopedPointer<ConfiguratorBase>();
}
}
ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials,
const DockerContainer container, QJsonObject &containerConfig)
{
ErrorCode errorCode = ErrorCode::NoError;
if (ContainerProps::containerService(container) == ServiceType::Other) {
return errorCode;
}
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
auto configurator = createConfigurator(protocol);
QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
protocolConfig.insert(config_key::last_config, protocolConfigString);
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
}
return errorCode;
}
ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns,
const ServerCredentials &credentials, const DockerContainer container,
const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString)
{
ErrorCode errorCode = ErrorCode::NoError;
if (ContainerProps::containerService(container) == ServiceType::Other) {
return errorCode;
}
auto configurator = createConfigurator(protocol);
protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString);
return errorCode;
}
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container,
ErrorCode errorCode)
{
QJsonObject vpnConfiguration {};
if (ContainerProps::containerService(container) == ServiceType::Other) {
return vpnConfiguration;
}
bool isApiConfig = serverConfig.value(config_key::configVersion).toInt();
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) {
continue;
}
QString protocolConfigString =
containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString();
auto configurator = createConfigurator(proto);
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
}
Proto proto = ContainerProps::defaultProtocol(container);
vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto);
vpnConfiguration[config_key::dns1] = dns.first;
vpnConfiguration[config_key::dns2] = dns.second;
vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString();
vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString();
vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt();
// TODO: try to get hostName, port, description for 3rd party configs
// vpnConfiguration[config_key::port] = ...;
return vpnConfiguration;
}
void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
if (container == DockerContainer::TorWebSite) {
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
qDebug() << "amnezia-tor onions" << stdOut;
QString onion = stdOut;
onion.replace("\n", "");
protocol.insert(config_key::site, onion);
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
}
}
@@ -0,0 +1,35 @@
#ifndef VPNCONFIGIRATIONSCONTROLLER_H
#define VPNCONFIGIRATIONSCONTROLLER_H
#include <QObject>
#include "configurators/configurator_base.h"
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "settings.h"
class VpnConfigurationsController : public QObject
{
Q_OBJECT
public:
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
public slots:
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
QJsonObject &containerConfig);
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode errorCode);
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
signals:
private:
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);
std::shared_ptr<Settings> m_settings;
};
#endif // VPNCONFIGIRATIONSCONTROLLER_H
+29 -16
View File
@@ -22,6 +22,20 @@ namespace amnezia
}
};
struct InstalledAppInfo {
QString appName;
QString packageName;
QString appPath;
bool operator==(const InstalledAppInfo& other) const {
if (!packageName.isEmpty()) {
return packageName == other.packageName;
} else {
return appPath == other.appPath;
}
}
};
enum ErrorCode {
// General error codes
NoError = 0,
@@ -46,25 +60,12 @@ namespace amnezia
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Ssh sftp errors
SshSftpEofError = 400,
SshSftpNoSuchFileError = 401,
SshSftpPermissionDeniedError = 402,
SshSftpFailureError = 403,
SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Ssh scp errors
SshScpFailureError = 400,
// Local errors
OpenVpnConfigMissing = 500,
OpenVpnManagementServerError = 501,
ConfigMissing = 502,
// Distro errors
OpenVpnExecutableMissing = 600,
@@ -72,6 +73,8 @@ namespace amnezia
CloakExecutableMissing = 602,
AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604,
XrayExecutableMissing = 605,
Tun2SockExecutableMissing = 606,
// VPN errors
OpenVpnAdaptersInUseError = 700,
@@ -83,6 +86,8 @@ namespace amnezia
OpenSslFailed = 800,
ShadowSocksExecutableCrashed = 801,
CloakExecutableCrashed = 802,
XrayExecutableCrashed = 803,
Tun2SockExecutableCrashed = 804,
// import and install errors
ImportInvalidConfigError = 900,
@@ -92,7 +97,15 @@ namespace amnezia
// Api errors
ApiConfigDownloadError = 1100,
ApiConfigAlreadyAdded = 1101
ApiConfigAlreadyAdded = 1101,
// QFile errors
OpenError = 1200,
ReadError = 1201,
PermissionsError = 1202,
UnspecifiedError = 1203,
FatalError = 1204,
AbortError = 1205
};
} // namespace amnezia
+11 -14
View File
@@ -19,6 +19,7 @@ QString errorString(ErrorCode code) {
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
// Libssh errors
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
@@ -28,20 +29,8 @@ QString errorString(ErrorCode code) {
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
// Libssh sftp errors
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Ssh scp errors
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
// Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
@@ -68,6 +57,14 @@ QString errorString(ErrorCode code) {
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
// QFile errors
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
case(InternalError):
default:
errorMessage = QObject::tr("Internal error"); break;
@@ -0,0 +1,12 @@
#include "installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
InstalledAppsImageProvider::InstalledAppsImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap)
{
}
QPixmap InstalledAppsImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
return AndroidController::instance()->getAppIcon(id, size, requestedSize);
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef INSTALLEDAPPSIMAGEPROVIDER_H
#define INSTALLEDAPPSIMAGEPROVIDER_H
#include <QObject>
#include <QQuickImageProvider>
class InstalledAppsImageProvider : public QQuickImageProvider
{
public:
InstalledAppsImageProvider();
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
};
#endif // INSTALLEDAPPSIMAGEPROVIDER_H
+1 -1
View File
@@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(1000);
futureResult.waitForFinished(5000);
int pid = futureResult.returnValue();
+462
View File
@@ -0,0 +1,462 @@
#include "networkUtilities.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <Ipexport.h>
#include <Ws2tcpip.h>
#include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h>
#include <Iptypes.h>
#include <WinSock2.h>
#include <winsock.h>
#include <QNetworkInterface>
#include "qendian.h"
#endif
#ifdef Q_OS_LINUX
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/route.h>
#endif
#include <QHostAddress>
#include <QHostInfo>
QRegularExpression NetworkUtilities::ipAddressRegExp()
{
return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$");
}
QRegularExpression NetworkUtilities::ipAddressPortRegExp()
{
return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$");
}
QRegExp NetworkUtilities::ipAddressWithSubnetRegExp()
{
return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}");
}
QRegExp NetworkUtilities::ipNetwork24RegExp()
{
return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"0$");
}
QRegExp NetworkUtilities::ipPortRegExp()
{
return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$");
}
QRegExp NetworkUtilities::domainRegExp()
{
return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-"
"9\\-]{1,30})\\.[a-z]{2,}");
}
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
{
if (!ip.contains("/"))
return "255.255.255.255";
bool ok;
int prefix = ip.split("/").at(1).toInt(&ok);
if (!ok)
return "255.255.255.255";
unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
}
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
{
if (ip.count(".") != 3)
return "";
return ip.split("/").first();
}
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
{
// QMap<int, int>
// QHostAddress
// QMap<QString, QStringList> subnets; // <"a.b", <list subnets>>
// for (const QString &ip : ips) {
// if (ip.count(".") != 3) continue;
// const QStringList &parts = ip.split(".");
// subnets[parts.at(0) + "." + parts.at(1)].append(ip);
// }
return QStringList();
}
QString NetworkUtilities::getIPAddress(const QString &host)
{
if (ipAddressRegExp().match(host).hasMatch()) {
return host;
}
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
if (!addresses.isEmpty()) {
return addresses.first().toString();
}
qDebug() << "Unable to resolve address for " << host;
return "";
}
QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b)
{
int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length());
if (ap < 0 || bp < 0)
return QString();
ap += a.length();
if (bp - ap <= 0)
return QString();
return s.mid(ap, bp - ap).trimmed();
}
bool NetworkUtilities::checkIPv4Format(const QString &ip)
{
if (ip.isEmpty())
return false;
int count = ip.count(".");
if (count != 3)
return false;
QHostAddress addr(ip);
return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol);
}
bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
{
if (!ip.contains("/"))
return checkIPv4Format(ip);
QStringList parts = ip.split("/");
if (parts.size() != 2)
return false;
bool ok;
int subnet = parts.at(1).toInt(&ok);
if (subnet >= 0 && subnet <= 32 && ok)
return checkIPv4Format(parts.at(0));
else
return false;
}
// static
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
if (result != NO_ERROR) {
return -1;
}
auto adapter =
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
qDebug() << "Internet Adapter:" << adapter.name();
return routeInfo.dwForwardIfIndex;
#endif
return 0;
}
#ifdef Q_OS_WIN
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
const ULONG Flags,
const PVOID Reserved,
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
DWORD dwRetVal = 0;
int iter = 0;
constexpr int max_iter = 3;
ULONG AdapterAddressesLen = 15000;
do {
// xassert2(pAdapterAddresses == nullptr);
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
if (pAdapterAddresses == nullptr) {
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
return ERROR_OUTOFMEMORY;
}
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
free(pAdapterAddresses);
pAdapterAddresses = nullptr;
} else {
break;
}
iter++;
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
if (dwRetVal != NO_ERROR) {
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
", dwRetVal:" << dwRetVal << ", iter: " << iter;
if (pAdapterAddresses) {
free(pAdapterAddresses);
pAdapterAddresses = nullptr;
}
}
return dwRetVal;
}
#endif
QString NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString result;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return "";
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
while (pCurAddress) {
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
if (gateway) {
SOCKET_ADDRESS gateway_address = gateway->Address;
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
qDebug() << "gateway IPV4:" << gw;
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
result = gw;
}
}
}
pCurAddress = pCurAddress->Next;
}
free(pAdapterAddresses);
return result;
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *)msgbuf;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
}
/* receive response */
do
{
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
}
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
{
switch(route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
break;
}
}
if ((*gateway_address) && (*interface)) {
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
}
close(sock);
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
QString gateway;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
for (int ip_type = 0; ip_type <= 1; ip_type++)
{
mib[3] = afinet_type[ip_type];
size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return "";
char* buf;
if ((buf = new char[needed]) == 0)
return "";
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{
qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf;
return gateway;
}
struct rt_msghdr* rt;
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
{
rt = reinterpret_cast<struct rt_msghdr*>(p);
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
struct sockaddr* sa_tab[RTAX_MAX];
for (int i = 0; i < RTAX_MAX; i++)
{
if (rt->rtm_addrs & (1 << i))
{
sa_tab[i] = sa;
sa = reinterpret_cast<struct sockaddr*>(
reinterpret_cast<char*>(sa) +
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
}
else
{
sa_tab[i] = nullptr;
}
}
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
{
if (afinet_type[ip_type] == AF_INET)
{
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{
char dstStr4[INET_ADDRSTRLEN];
char srcStr4[INET_ADDRSTRLEN];
memcpy(srcStr4,
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
gateway = dstStr4;
break;
}
}
else if (afinet_type[ip_type] == AF_INET6)
{
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{
char dstStr6[INET6_ADDRSTRLEN];
char srcStr6[INET6_ADDRSTRLEN];
memcpy(srcStr6,
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
gateway = dstStr6;
break;
}
}
}
}
free(buf);
}
return gateway;
#endif
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef NETWORKUTILITIES_H
#define NETWORKUTILITIES_H
#include <QRegularExpression>
#include <QRegExp>
#include <QString>
#include <QHostAddress>
class NetworkUtilities : public QObject
{
Q_OBJECT
public:
static QString getIPAddress(const QString &host);
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip);
static QString getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
static QRegularExpression ipAddressRegExp();
static QRegularExpression ipAddressPortRegExp();
static QRegExp ipAddressWithSubnetRegExp();
static QRegExp ipNetwork24RegExp();
static QRegExp ipPortRegExp();
static QRegExp domainRegExp();
static QString netMaskFromIpWithSubnet(const QString ip);
static QString ipAddressFromIpWithSubnet(const QString ip);
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
};
#endif // NETWORKUTILITIES_H
+3 -1
View File
@@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg: return QLatin1String("awg");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::Sftp: return QLatin1String("sftp");
default: return "";
default: return QString();
}
}
@@ -47,6 +48,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
default: return QString();
}
}
+2 -1
View File
@@ -27,7 +27,8 @@ enum ProtocolScriptType {
container_startup,
openvpn_template,
wireguard_template,
awg_template
awg_template,
xray_template
};
+56 -90
View File
@@ -10,16 +10,10 @@ const uint32_t S_IRWXU = 0644;
#endif
namespace libssh {
const QString libsshTimeoutError = "Timeout connecting to";
constexpr auto libsshTimeoutError{"Timeout connecting to"};
std::function<QString()> Client::m_passphraseCallback;
Client::Client(QObject *parent) : QObject(parent)
{ }
Client::~Client()
{ }
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
{
auto passphrase = m_passphraseCallback();
@@ -171,13 +165,13 @@ namespace libssh {
return ErrorCode::NoError;
};
auto error = readOutput(false);
if (error != ErrorCode::NoError) {
return error;
auto errorCode = readOutput(false);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
error = readOutput(true);
if (error != ErrorCode::NoError) {
return error;
errorCode = readOutput(true);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
} else {
return closeChannel();
@@ -222,100 +216,79 @@ namespace libssh {
return fromLibsshErrorCode();
}
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
{
m_sftpSession = sftp_new(m_session);
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
if (m_sftpSession == nullptr) {
return closeSftpSession();
if (m_scpSession == nullptr) {
return fromLibsshErrorCode();
}
int result = sftp_init(m_sftpSession);
if (result != SSH_OK) {
return closeSftpSession();
if (ssh_scp_init(m_scpSession) != SSH_OK) {
auto errorCode = fromLibsshErrorCode();
closeScpSession();
return errorCode;
}
QFutureWatcher<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished);
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
int accessType = O_WRONLY | O_CREAT | overwriteMode;
sftp_file file;
const size_t bufferSize = 16384;
char buffer[bufferSize];
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
const int localFileSize = QFileInfo(localPath).size();
file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU);
if (file == nullptr) {
return closeSftpSession();
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType);
if (result != SSH_OK) {
return fromLibsshErrorCode();
}
int localFileSize = QFileInfo(localPath).size();
int chunksCount = localFileSize / (bufferSize);
QFile fin(localPath);
if (fin.open(QIODevice::ReadOnly)) {
for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) {
QByteArray chunk = fin.read(bufferSize);
if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError;
constexpr size_t bufferSize = 16384;
int transferred = 0;
int currentChunkSize = bufferSize;
int bytesWritten = sftp_write(file, chunk.data(), chunk.size());
while (transferred < localFileSize) {
if (bytesWritten != chunk.size()) {
fin.close();
sftp_close(file);
return closeSftpSession();
// Last Chunk
if ((localFileSize - transferred) < bufferSize) {
currentChunkSize = localFileSize % bufferSize;
}
}
int lastChunkSize = localFileSize % bufferSize;
if (lastChunkSize != 0) {
QByteArray lastChunk = fin.read(lastChunkSize);
if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError;
int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize);
if (bytesWritten != lastChunkSize) {
fin.close();
sftp_close(file);
return closeSftpSession();
QByteArray chunk = fin.read(currentChunkSize);
if (chunk.size() != currentChunkSize) {
return fromFileErrorCode(fin.error());
}
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size());
if (result != SSH_OK) {
return fromLibsshErrorCode();
}
transferred += currentChunkSize;
}
} else {
sftp_close(file);
return closeSftpSession();
return fromFileErrorCode(fin.error());
}
fin.close();
int result = sftp_close(file);
if (result != SSH_OK) {
return closeSftpSession();
}
return closeSftpSession();
return ErrorCode::NoError;
});
watcher.setFuture(future);
QEventLoop wait;
QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit);
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec();
closeScpSession();
return watcher.result();
}
ErrorCode Client::closeSftpSession()
void Client::closeScpSession()
{
auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession));
if (m_sftpSession != nullptr) {
sftp_free(m_sftpSession);
m_sftpSession = nullptr;
if (m_scpSession != nullptr) {
ssh_scp_free(m_scpSession);
m_scpSession = nullptr;
}
qCritical() << ssh_get_error(m_session);
return errorCode;
}
ErrorCode Client::fromLibsshErrorCode()
@@ -337,24 +310,17 @@ namespace libssh {
default: return ErrorCode::SshInternalError;
}
}
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
{
switch (errorCode) {
case(SSH_FX_OK): return ErrorCode::NoError;
case(SSH_FX_EOF): return ErrorCode::SshSftpEofError;
case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError;
case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError;
case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError;
case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError;
case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError;
case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError;
case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError;
case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError;
case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError;
case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError;
case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError;
case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError;
default: return ErrorCode::SshSftpFailureError;
switch (fileError) {
case QFileDevice::NoError: return ErrorCode::NoError;
case QFileDevice::ReadError: return ErrorCode::ReadError;
case QFileDevice::OpenError: return ErrorCode::OpenError;
case QFileDevice::PermissionsError: return ErrorCode::PermissionsError;
case QFileDevice::FatalError: return ErrorCode::FatalError;
case QFileDevice::AbortError: return ErrorCode::AbortError;
default: return ErrorCode::UnspecifiedError;
}
}
+12 -12
View File
@@ -2,29 +2,29 @@
#define SSHCLIENT_H
#include <QObject>
#include <QFile>
#include <fcntl.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include "defs.h"
using namespace amnezia;
namespace libssh {
enum SftpOverwriteMode {
enum ScpOverwriteMode {
/*! Overwrite any existing files */
SftpOverwriteExisting = O_TRUNC,
ScpOverwriteExisting = O_TRUNC,
/*! Append new content if the file already exists */
SftpAppendToExisting = O_APPEND
ScpAppendToExisting = O_APPEND
};
class Client : public QObject
{
Q_OBJECT
public:
Client(QObject *parent = nullptr);
~Client();
Client() = default;
~Client() = default;
ErrorCode connectToHost(const ServerCredentials &credentials);
void disconnectFromHost();
@@ -32,26 +32,26 @@ namespace libssh {
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
ErrorCode writeResponse(const QString &data);
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
ErrorCode scpFileCopy(const ScpOverwriteMode overwriteMode,
const QString &localPath,
const QString &remotePath,
const QString& fileDesc);
const QString &fileDesc);
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
private:
ErrorCode closeChannel();
ErrorCode closeSftpSession();
void closeScpSession();
ErrorCode fromLibsshErrorCode();
ErrorCode fromLibsshSftpErrorCode(int errorCode);
ErrorCode fromFileErrorCode(QFileDevice::FileError fileError);
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
ssh_session m_session = nullptr;
ssh_channel m_channel = nullptr;
sftp_session m_sftpSession = nullptr;
ssh_scp m_scpSession = nullptr;
static std::function<QString()> m_passphraseCallback;
signals:
void writeToChannelFinished();
void sftpFileCopyFinished();
void scpFileCopyFinished();
};
}
+13
View File
@@ -251,6 +251,19 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
GETVALUE("serverPskKey", config.m_serverPskKey, String);
GETVALUE("serverPort", config.m_serverPort, Double);
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
{
config.m_deviceMTU = 1420;
} else {
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
#ifdef Q_OS_WINDOWS
// For Windows min MTU value is 1280 (the smallest MTU legal with IPv6).
if (config.m_deviceMTU < 1280) {
config.m_deviceMTU = 1280;
}
#endif
}
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
if (config.m_deviceIpv4Address.isNull() &&
+4 -2
View File
@@ -35,8 +35,10 @@ class Daemon : public QObject {
virtual QJsonObject getStatus();
// Callback before any Activating measure is done
virtual void prepareActivation(const InterfaceConfig& config){
Q_UNUSED(config)};
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
Q_UNUSED(config) };
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
Q_UNUSED(config) };
QString logs();
void cleanLogs();
+6
View File
@@ -23,6 +23,7 @@ QJsonObject InterfaceConfig::toJson() const {
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
json.insert("serverPort", QJsonValue((double)m_serverPort));
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
(m_hopType == InterfaceConfig::SingleHop)) {
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
@@ -85,8 +86,13 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
if (addresses.isEmpty()) {
return "";
}
out << "Address = " << addresses.join(", ") << "\n";
if (m_deviceMTU) {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_dnsServer.isNull()) {
QStringList dnsServers(m_dnsServer);
// If the DNS is not the Gateway, it's a user defined DNS
+1
View File
@@ -33,6 +33,7 @@ class InterfaceConfig {
QString m_serverIpv6AddrIn;
QString m_dnsServer;
int m_serverPort = 0;
int m_deviceMTU = 1420;
QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps;
+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8V12" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 16H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V7.5L14.5 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 15L5 17L9 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

+2 -1
View File
@@ -84,7 +84,8 @@ target_sources(networkextension PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
)
+3 -6
View File
@@ -26,9 +26,10 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(ASFW_ANY);
#endif
// QTBUG-95974 QTBUG-95764 QTBUG-102168
#ifdef Q_OS_ANDROID
// QTBUG-95974 QTBUG-95764 QTBUG-102168
qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1");
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@@ -48,10 +49,6 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(0);
#endif
#if defined(Q_OS_IOS)
QtAppDelegateInitialize();
#endif
app.registerTypes();
app.setApplicationName(APPLICATION_NAME);
@@ -65,7 +62,7 @@ int main(int argc, char *argv[])
if (doExec) {
app.init();
qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION);
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
return app.exec();
+14 -11
View File
@@ -117,6 +117,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
QJsonObject json;
@@ -124,16 +127,21 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
// todo review wg ipv6
#ifdef Q_OS_MACOS
json.insert("deviceIpv6Address", "dead::1");
#endif
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
// Otherwise some OSes (Linux) try IPv6 forever and hang.
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("deviceMTU", wgConfig.value(amnezia::config_key::mtu));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
@@ -212,12 +220,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("excludedAddresses", jsExcludedAddresses);
// QJsonArray splitTunnelApps;
// for (const auto& uri : hop.m_vpnDisabledApps) {
// splitTunnelApps.append(QJsonValue(uri));
// }
// json.insert("vpnDisabledApps", splitTunnelApps);
json.insert("vpnDisabledApps", splitTunnelApps);
if (protocolName == amnezia::config_key::awg) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
+66 -42
View File
@@ -2,6 +2,9 @@
#include <QJsonDocument>
#include <QQmlFile>
#include <QEventLoop>
#include <QImage>
#include <android/bitmap.h>
#include "android_controller.h"
#include "android_utils.h"
@@ -56,26 +59,10 @@ AndroidController::AndroidController() : QObject()
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnConnected, this,
[this]() {
qDebug() << "Android event: VPN connected";
emit connectionStateChanged(Vpn::ConnectionState::Connected);
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnDisconnected, this,
[this]() {
qDebug() << "Android event: VPN disconnected";
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnReconnecting, this,
[this]() {
qDebug() << "Android event: VPN reconnecting";
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
this, &AndroidController::vpnStateChanged, this,
[this](AndroidController::ConnectionState state) {
qDebug() << "Android event: VPN state changed:" << textConnectionState(state);
emit connectionStateChanged(convertState(state));
},
Qt::QueuedConnection);
@@ -106,9 +93,7 @@ bool AndroidController::initialize()
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
@@ -158,6 +143,11 @@ void AndroidController::stop()
callActivityMethod("stop", "()V");
}
void AndroidController::resetLastServer(int serverIndex)
{
callActivityMethod("resetLastServer", "(I)V", serverIndex);
}
void AndroidController::saveFile(const QString &fileName, const QString &data)
{
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
@@ -217,6 +207,56 @@ void AndroidController::clearLogs()
callActivityMethod("clearLogs", "()V");
}
void AndroidController::setScreenshotsEnabled(bool enabled)
{
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
}
void AndroidController::minimizeApp()
{
callActivityMethod("minimizeApp", "()V");
}
QJsonArray AndroidController::getAppList()
{
QJniObject appList = callActivityMethod<jstring>("getAppList", "()Ljava/lang/String;");
QJsonArray jsonAppList = QJsonDocument::fromJson(appList.toString().toUtf8()).array();
return jsonAppList;
}
QPixmap AndroidController::getAppIcon(const QString &package, QSize *size, const QSize &requestedSize)
{
QJniObject bitmap = callActivityMethod<jobject>("getAppIcon", "(Ljava/lang/String;II)Landroid/graphics/Bitmap;",
QJniObject::fromString(package).object<jstring>(),
requestedSize.width(), requestedSize.height());
QJniEnvironment env;
AndroidBitmapInfo info;
if (AndroidBitmap_getInfo(env.jniEnv(), bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
void *pixels;
if (AndroidBitmap_lockPixels(env.jniEnv(), bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
int width = info.width;
int height = info.height;
size->setWidth(width);
size->setHeight(height);
QImage image(width, height, QImage::Format_RGBA8888);
if (info.stride == uint32_t(image.bytesPerLine())) {
memcpy((void *) image.constBits(), pixels, info.stride * height);
} else {
auto *bmpPtr = static_cast<uchar *>(pixels);
for (int i = 0; i < height; i++, bmpPtr += info.stride)
memcpy((void *) image.constScanLine(i), bmpPtr, width);
}
if (AndroidBitmap_unlockPixels(env.jniEnv(), bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
return QPixmap::fromImage(image);
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
@@ -370,30 +410,14 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
}
// static
void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz)
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnConnected();
}
auto state = ConnectionState(stateCode);
// static
void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnDisconnected();
}
// static
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnReconnecting();
emit AndroidController::instance()->vpnStateChanged(state);
}
// static
@@ -2,6 +2,7 @@
#define ANDROID_CONTROLLER_H
#include <QJniObject>
#include <QPixmap>
#include "protocols/vpnprotocol.h"
@@ -20,9 +21,9 @@ public:
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState
{
DISCONNECTED,
CONNECTED,
CONNECTING,
DISCONNECTED,
DISCONNECTING,
RECONNECTING,
UNKNOWN
@@ -30,6 +31,7 @@ public:
ErrorCode start(const QJsonObject &vpnConfig);
void stop();
void resetLastServer(int serverIndex);
void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
@@ -38,6 +40,10 @@ public:
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
void setScreenshotsEnabled(bool enabled);
void minimizeApp();
QJsonArray getAppList();
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@@ -48,9 +54,7 @@ signals:
void serviceDisconnected();
void serviceError();
void vpnPermissionRejected();
void vpnConnected();
void vpnDisconnected();
void vpnReconnecting();
void vpnStateChanged(ConnectionState state);
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri);
void configImported(QString config);
@@ -77,9 +81,7 @@ private:
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
static void onServiceError(JNIEnv *env, jobject thiz);
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
static void onVpnConnected(JNIEnv *env, jobject thiz);
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
+31 -4
View File
@@ -2,6 +2,8 @@ import Foundation
import os.log
struct Log {
static let osLog = Logger()
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
static var isLoggingEnabled: Bool {
get {
@@ -29,16 +31,23 @@ struct Log {
return dateFormatter
}()
var records: [Record]
var records = [Record]()
var lastRecordDate = Date.distantPast
init() {
self.records = []
}
init(_ str: String) {
self.records = str.split(whereSeparator: \.isNewline)
.compactMap {
Record(String($0))!
records = str.split(whereSeparator: \.isNewline)
.map {
if let record = Record(String($0)) {
lastRecordDate = record.date
return record
} else {
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
}
}
}
@@ -60,6 +69,24 @@ struct Log {
self.init(str)
}
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
guard isLoggingEnabled else { return }
let date = Date()
let level = Record.Level(from: type)
let messages = message.split(whereSeparator: \.isNewline)
for index in 0..<messages.count {
let message = String(messages[index])
if index != 0 && message.first != " " {
Record(date: date, level: level, message: "\(title) \(message)").save(at: url)
} else {
Record(date: date, level: level, message: "\(title)\(message)").save(at: url)
}
}
}
static func clear(at url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
+24
View File
@@ -1,4 +1,5 @@
import Foundation
import NetworkExtension
public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
let qtLog = Log(String(describing: qtString))
@@ -24,3 +25,26 @@ public func swiftDeleteLog() {
public func toggleLogging(_ isEnabled: Bool) {
Log.isLoggingEnabled = isEnabled
}
public func clearSettings() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let error {
NSLog("clearSettings removeFromPreferences error: \(error.localizedDescription)")
return
}
managers?.forEach { manager in
manager.removeFromPreferences { error in
if let error {
NSLog("NE removeFromPreferences error: \(error.localizedDescription)")
} else {
manager.loadFromPreferences { error in
if let error {
NSLog("NE loadFromPreferences after remove error: \(error.localizedDescription)")
}
}
}
}
}
}
}
+26 -5
View File
@@ -30,6 +30,8 @@ extension Log {
}
func save(at url: URL) {
osLog.log(level: level.osLogType, "\(message)")
guard let data = "\n\(description)".data(using: .utf8) else { return }
if !FileManager.default.fileExists(atPath: url.path) {
@@ -64,19 +66,38 @@ extension Log.Record {
init(from osLogType: OSLogType) {
switch osLogType {
case OSLogType.default:
case .default:
self = .info
case OSLogType.info:
case .info:
self = .info
case OSLogType.debug:
case .debug:
self = .debug
case OSLogType.error:
case .error:
self = .error
case OSLogType.fault:
case .fault:
self = .fatal
default:
self = .info
}
}
var osLogType: OSLogType {
switch self {
case .info:
return .info
case .debug:
return .debug
case .error:
return .error
case .fatal:
return .fault
case .warning:
return .info
case .critical:
return .fault
case .system:
return .fault
}
}
}
}
+10 -10
View File
@@ -1,18 +1,18 @@
import Foundation
import os.log
public func wg_log(_ type: OSLogType, staticMessage: StaticString) {
guard Log.isLoggingEnabled else { return }
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: "\(staticMessage)").save(at: Log.neLogURL)
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
}
public func wg_log(_ type: OSLogType, message: String) {
log(type, message: message)
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "WG: \(title)", message: message)
}
public func log(_ type: OSLogType, message: String) {
guard Log.isLoggingEnabled else { return }
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: message).save(at: Log.neLogURL)
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "OVPN: \(title)", message: message)
}
public func neLog(_ type: OSLogType, title: String = "", message: String) {
Log.log(type, title: "NE: \(title)", message: message)
}
@@ -0,0 +1,223 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
struct OpenVPNConfig: Decodable {
let config: String
let splitTunnelType: Int
let splitTunnelSites: [String]
var str: String {
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
}
}
extension PacketTunnelProvider {
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
ovpnLog(.error, message: "Can't start")
return
}
do {
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
}
return
}
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
withShadowSocks viaSS: Bool = false,
completionHandler: @escaping (Error?) -> Void) {
ovpnLog(.info, message: "Setup and launch")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.setPTCloak()
}
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try ovpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
if !evaluation.autologin {
ovpnLog(.info, message: "Implement login with user credentials")
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
ovpnAdapter.connect(using: packetFlow)
// let ifaces = Interface.allInterfaces()
// .filter { $0.family == .ipv4 }
// .map { iface in iface.name }
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter.transportStatistics.bytesIn
let bytesout = ovpnAdapter.transportStatistics.bytesOut
let response: [String: Any] = [
"rx_bytes": bytesin,
"tx_bytes": bytesout
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
ovpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: @escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]()
for allowedIPString in splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
for excludeIPString in splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)",
subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event: OpenVPNAdapterEvent,
message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -1,129 +0,0 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: @escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == "1" {
var ipv4IncludedRoutes = [NEIPv4Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if splitTunnelType == "2" {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for excludeIPString in STSArray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)",
subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event: OpenVPNAdapterEvent,
message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
wg_log(.info, message: logMessage)
}
}
@@ -0,0 +1,221 @@
import Foundation
import NetworkExtension
extension PacketTunnelProvider {
func startWireguard(activationAttemptId: String?,
errorNotifier: ErrorNotifier,
completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start, config missing")
completionHandler(nil)
return
}
do {
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
let wgConfigStr = wgConfig.str
wg_log(.info, title: "config: ", message: wgConfig.redux)
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
if tunnelConfiguration.peers.first!.allowedIPs
.map({ $0.stringRepresentation })
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
if wgConfig.splitTunnelType == 1 {
for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]()
for allowedIPString in wgConfig.splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP)
}
}
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
}
} else if wgConfig.splitTunnelType == 2 {
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]()
for excludeIPString in wgConfig.splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP)
}
}
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
}
}
}
wg_log(.info, message: "Starting tunnel from the " +
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// Start the tunnel
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
guard let adapterError else {
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil)
return
}
switch adapterError {
case .cannotLocateTunnelFileDescriptor:
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
case .dnsResolution(let dnsErrors):
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
.joined(separator: ", ")
wg_log(.error, message:
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
case .setNetworkSettings(let error):
wg_log(.error, message:
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
case .startWireGuardBackend(let errorCode):
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
case .invalidState:
fatalError()
}
}
} catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
completionHandler(nil)
return
}
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
for component in components {
let pair = component.components(separatedBy: "=")
if pair.count == 2 {
settingsDictionary[pair[0]] = pair[1]
}
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
}
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
} else if messageData.count >= 1 {
// Updates the tunnel configuration and responds with the active configuration
wg_log(.info, message: "Switching tunnel configuration")
guard let configString = String(data: messageData, encoding: .utf8)
else {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
if let error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil)
return
}
self.wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
}
} catch {
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
//
// let emptyTunnelConfiguration = TunnelConfiguration(
// name: nil,
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
// peers: []
// )
//
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
// self.dispatchQueue.async {
// if let error {
// wg_log(.error, message: "Failed to start an empty tunnel")
// completionHandler(error)
// } else {
// wg_log(.info, 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))
// // ...
// }
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, staticMessage: "Stopping tunnel")
wgAdapter.stop { error in
ErrorNotifier.removeLastErrorFile()
if let error {
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
}
completionHandler()
#if os(macOS)
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
// Remove it when they finally fix this upstream and the fix has been rolled out to
// sufficient quantities of users.
exit(0)
#endif
}
}
}
+38 -348
View File
@@ -15,7 +15,7 @@ struct Constants {
static let ovpnConfigKey = "ovpn"
static let wireGuardConfigKey = "wireguard"
static let loggerTag = "NET"
static let kActionStart = "start"
static let kActionRestart = "restart"
static let kActionStop = "stop"
@@ -34,78 +34,68 @@ struct Constants {
}
class PacketTunnelProvider: NEPacketTunnelProvider {
private lazy var wgAdapter = {
lazy var wgAdapter = {
WireGuardAdapter(with: self) { logLevel, message in
wg_log(logLevel.osLogLevel, message: message)
}
}()
private lazy var ovpnAdapter: OpenVPNAdapter = {
lazy var ovpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
/// Internal queue.
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
private var openVPNConfig: Data?
var splitTunnelType: String?
var splitTunnelSites: String?
var splitTunnelType: Int!
var splitTunnelSites: [String]!
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType = .none
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
let tmpStr = String(data: messageData, encoding: .utf8)!
wg_log(.error, message: tmpStr)
guard let message = String(data: messageData, encoding: .utf8) else {
if let completionHandler {
completionHandler(nil)
}
return
}
neLog(.info, title: "App said: ", message: message)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
log(.error, message: "Failed to serialize message from app")
neLog(.error, message: "Failed to serialize message from app")
return
}
guard let completionHandler else {
log(.error, message: "Missing message completion handler")
neLog(.error, message: "Missing message completion handler")
return
}
guard let action = message[Constants.kMessageKeyAction] as? String else {
log(.error, message: "Missing action key in app message")
neLog(.error, message: "Missing action key in app message")
completionHandler(nil)
return
}
if action == Constants.kActionStatus {
handleStatusAppMessage(messageData, completionHandler: completionHandler)
}
if action == Constants.kActionStart {
splitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
splitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
}
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)
log(.info, message: "PacketTunnelProvider startTunnel")
neLog(.info, message: "Start tunnel")
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
let providerConfiguration = protocolConfiguration.providerConfiguration
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
@@ -116,7 +106,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} else {
self.protoType = .none
}
switch self.protoType {
case .wireguard:
self.startWireguard(activationAttemptId: activationAttemptId,
@@ -132,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
}
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
dispatchQueue.async {
switch self.protoType {
@@ -148,7 +138,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
}
}
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
switch protoType {
case .wireguard:
@@ -162,283 +152,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
break
}
}
// MARK: Private methods
private func startWireguard(activationAttemptId: String?,
errorNotifier: ErrorNotifier,
completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start WireGuard config missing")
completionHandler(nil)
return
}
guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str,
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
else {
wg_log(.error, message: "Can't parse WireGuard config")
completionHandler(nil)
return
}
log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))")
if tunnelConfiguration.peers.first!.allowedIPs
.map({ $0.stringRepresentation })
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
if splitTunnelType == "1" {
for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP)
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
}
} else if splitTunnelType == "2" {
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for excludeIPString in STSArray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP)
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
}
}
}
wg_log(.info, message: "Starting wireguard tunnel from the " +
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// Start the tunnel
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
guard let adapterError else {
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil)
return
}
switch adapterError {
case .cannotLocateTunnelFileDescriptor:
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
case .dnsResolution(let dnsErrors):
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
.joined(separator: ", ")
wg_log(.error, message:
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
case .setNetworkSettings(let error):
wg_log(.error, message:
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
case .startWireGuardBackend(let errorCode):
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
case .invalidState:
fatalError()
}
}
}
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
wg_log(.error, message: "Can't start startOpenVPN()")
return
}
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
}
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, staticMessage: "Stopping tunnel")
wgAdapter.stop { error in
ErrorNotifier.removeLastErrorFile()
if let error {
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
}
completionHandler()
#if os(macOS)
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
// Remove it when they finally fix this upstream and the fix has been rolled out to
// sufficient quantities of users.
exit(0)
#endif
}
}
private func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
ovpnAdapter.disconnect()
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
for component in components {
let pair = component.components(separatedBy: "=")
if pair.count == 2 {
settingsDictionary[pair[0]] = pair[1]
}
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
}
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
} else if messageData.count >= 1 {
// Updates the tunnel configuration and responds with the active configuration
wg_log(.info, message: "Switching tunnel configuration")
guard let configString = String(data: messageData, encoding: .utf8)
else {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
if let error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil)
return
}
self.wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
}
} catch {
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter.transportStatistics.bytesIn
let bytesout = ovpnAdapter.transportStatistics.bytesOut
let response: [String: Any] = [
"rx_bytes": bytesin,
"tx_bytes": bytesout
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
withShadowSocks viaSS: Bool = false,
completionHandler: @escaping (Error?) -> Void) {
wg_log(.info, message: "setupAndlaunchOpenVPN")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.setPTCloak()
}
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try ovpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
if !evaluation.autologin {
wg_log(.info, message: "Implement login with user credentials")
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
ovpnAdapter.connect(using: packetFlow)
// let ifaces = Interface.allInterfaces()
// .filter { $0.family == .ipv4 }
// .map { iface in iface.name }
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
}
// MARK: Network observing methods
private func startListeningForNetworkChanges() {
stopListeningForNetworkChanges()
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
}
private func stopListeningForNetworkChanges() {
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
@@ -458,48 +183,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
let emptyTunnelConfiguration = TunnelConfiguration(
name: nil,
interface: InterfaceConfiguration(privateKey: PrivateKey()),
peers: []
)
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
self.dispatchQueue.async {
if let error {
log(.error, message: "Failed to start an empty tunnel")
completionHandler(error)
} else {
log(.info, 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 WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {
+4 -1
View File
@@ -1,4 +1,7 @@
#import <UIKit/UIKit.h>
@interface QtAppDelegate : UIResponder <UIApplicationDelegate>
@interface QIOSApplicationDelegate
@end
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
@end
+16 -55
View File
@@ -3,41 +3,17 @@
#include <QFile>
@implementation QtAppDelegate {
UIView *_screen;
}
+(QtAppDelegate *)sharedQtAppDelegate {
static dispatch_once_t pred;
static QtAppDelegate *shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] init];
});
return shared;
}
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
// Override point for customization after application launch.
NSLog(@"Did this launch option happen");
NSLog(@"Application didFinishLaunchingWithOptions");
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
@@ -51,17 +27,6 @@
NSLog(@"In the foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[_screen removeFromSuperview];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// We will add content here soon.
NSLog(@"In the completionHandler");
@@ -70,31 +35,27 @@
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
NSLog(@"Application openURL: %@", url);
if (url.fileURL) {
QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) return NO;
if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath);
} else {
QFile file(filePath);
bool isOpenFile = file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString(data));
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"Application openURL: %@", url);
if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath);
} else {
QFile file(filePath);
bool isOpenFile = file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString(data));
}
});
return YES;
}
return NO;
}
void QtAppDelegateInitialize()
{
[[UIApplication sharedApplication] setDelegate: [QtAppDelegate sharedQtAppDelegate]];
NSLog(@"Created a new AppDelegate");
}
@end
@@ -0,0 +1,87 @@
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
var keyWindows: [UIWindow] {
connectedScenes
.compactMap {
if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow
} else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
}
}
}
}
class ScreenProtection {
public static let shared = ScreenProtection()
var pairs = [ProtectionPair]()
private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation?
public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
view.subviews.forEach {
self.pairs.append(ProtectionPair(from: $0))
}
}
}
public func disable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.pairs.forEach {
$0.removeProtection()
}
self.pairs.removeAll()
}
}
}
struct ProtectionPair {
let textField: UITextField
let layer: CALayer
init(from view: UIView) {
let secureTextField = UITextField()
secureTextField.backgroundColor = .clear
secureTextField.translatesAutoresizingMaskIntoConstraints = false
secureTextField.isSecureTextEntry = true
view.insertSubview(secureTextField, at: 0)
secureTextField.isUserInteractionEnabled = false
view.layer.superlayer?.addSublayer(secureTextField.layer)
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
self.init(textField: secureTextField, layer: view.layer)
}
init(textField: UITextField, layer: CALayer) {
self.textField = textField
self.layer = layer
}
func removeProtection() {
textField.superview?.superview?.layer.addSublayer(layer)
textField.layer.removeFromSuperlayer()
textField.removeFromSuperview()
}
}
+60 -104
View File
@@ -1,10 +1,43 @@
import Foundation
struct WGConfigData: Decodable {
struct WGConfig: Decodable {
let initPacketMagicHeader, responsePacketMagicHeader: String?
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize: String?
let dns1: String
let dns2: String
let mtu: String
let hostName: String
let port: Int
let clientIP: String
let clientPrivateKey: String
let serverPublicKey: String
let presharedKey: String
var allowedIPs: [String]
var persistentKeepAlive: String
let splitTunnelType: Int
let splitTunnelSites: [String]
enum CodingKeys: String, CodingKey {
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case dns1
case dns2
case mtu
case hostName
case port
case clientIP = "client_ip"
case clientPrivateKey = "client_priv_key"
case serverPublicKey = "server_pub_key"
case presharedKey = "psk_key"
case allowedIPs = "allowed_ips"
case persistentKeepAlive = "persistent_keep_alive"
case splitTunnelType
case splitTunnelSites
}
var settings: String {
junkPacketCount == nil ? "" :
@@ -22,114 +55,37 @@ struct WGConfigData: Decodable {
"""
}
let clientIP: String
let clientPrivateKey: String
let clientPublicKey: String
let serverPublicKey: String
let presharedKey: String
let hostName: String
let port: Int
var allowedIPs: [String]
var persistentKeepAlive: String
enum CodingKeys: String, CodingKey {
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case clientIP = "client_ip" // "10.8.1.16"
case clientPrivateKey = "client_priv_key"
case clientPublicKey = "client_pub_key"
case serverPublicKey = "server_pub_key"
case presharedKey = "psk_key"
case allowedIPs = "allowed_ips"
case persistentKeepAlive = "persistent_keep_alive"
case hostName
case port
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.initPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .initPacketMagicHeader)
self.responsePacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .responsePacketMagicHeader)
self.underloadPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .underloadPacketMagicHeader)
self.transportPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .transportPacketMagicHeader)
self.junkPacketCount = try container.decodeIfPresent(String.self, forKey: .junkPacketCount)
self.junkPacketMinSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMinSize)
self.junkPacketMaxSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMaxSize)
self.initPacketJunkSize = try container.decodeIfPresent(String.self, forKey: .initPacketJunkSize)
self.responsePacketJunkSize = try container.decodeIfPresent(String.self, forKey: .responsePacketJunkSize)
self.clientIP = try container.decode(String.self, forKey: .clientIP)
self.clientPrivateKey = try container.decode(String.self, forKey: .clientPrivateKey)
self.clientPublicKey = try container.decode(String.self, forKey: .clientPublicKey)
self.serverPublicKey = try container.decode(String.self, forKey: .serverPublicKey)
self.presharedKey = try container.decode(String.self, forKey: .presharedKey)
self.allowedIPs = try container.decodeIfPresent([String].self, forKey: .allowedIPs) ?? ["0.0.0.0/0", "::/0"]
self.persistentKeepAlive = try container.decodeIfPresent(String.self, forKey: .persistentKeepAlive) ?? "25"
self.hostName = try container.decode(String.self, forKey: .hostName)
self.port = try container.decode(Int.self, forKey: .port)
}
}
struct WGConfig: Decodable {
let data: WGConfigData
let configVersion: Int
let description: String
let dns1: String
let dns2: String
let hostName: String
let `protocol`: String
let splitTunnelSites: [String]
let splitTunnelType: Int
enum CodingKeys: String, CodingKey {
case awgConfigData = "awg_config_data", wgConfigData = "wireguard_config_data"
case configData
case configVersion = "config_version"
case description
case dns1
case dns2
case hostName
case `protocol`
case splitTunnelSites
case splitTunnelType
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.awgConfigData) {
self.data = try container.decode(WGConfigData.self, forKey: .awgConfigData)
} else {
self.data = try container.decode(WGConfigData.self, forKey: .wgConfigData)
}
self.configVersion = try container.decode(Int.self, forKey: .configVersion)
self.description = try container.decode(String.self, forKey: .description)
self.dns1 = try container.decode(String.self, forKey: .dns1)
self.dns2 = try container.decode(String.self, forKey: .dns2)
self.hostName = try container.decode(String.self, forKey: .hostName)
self.protocol = try container.decode(String.self, forKey: .protocol)
self.splitTunnelSites = try container.decode([String].self, forKey: .splitTunnelSites)
self.splitTunnelType = try container.decode(Int.self, forKey: .splitTunnelType)
}
var str: String {
"""
[Interface]
Address = \(data.clientIP)/32
Address = \(clientIP)
DNS = \(dns1), \(dns2)
PrivateKey = \(data.clientPrivateKey)
\(data.settings)
MTU = \(mtu)
PrivateKey = \(clientPrivateKey)
\(settings)
[Peer]
PublicKey = \(data.serverPublicKey)
PresharedKey = \(data.presharedKey)
AllowedIPs = \(data.allowedIPs.joined(separator: ", "))
Endpoint = \(data.hostName):\(data.port)
PersistentKeepalive = \(data.persistentKeepAlive)
PublicKey = \(serverPublicKey)
PresharedKey = \(presharedKey)
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive)
"""
}
var redux: String {
"""
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = ***
\(settings)
[Peer]
PublicKey = ***
PresharedKey = ***
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive)
"""
}
}
+150 -43
View File
@@ -235,7 +235,6 @@ void IosController::checkStatus()
m_rxBytes = rxBytes;
m_txBytes = txBytes;
});
}
void IosController::vpnStatusDidChange(void *pNotification)
@@ -244,13 +243,13 @@ void IosController::vpnStatusDidChange(void *pNotification)
if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
@@ -315,11 +314,11 @@ void IosController::vpnStatusDidChange(void *pNotification)
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
@@ -342,7 +341,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
}
}
@@ -357,7 +356,29 @@ bool 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);
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
}
bool IosController::setupCloak()
@@ -394,27 +415,137 @@ bool IosController::setupCloak()
ovpnConfig.append(cloakBase64);
ovpnConfig.append("\n</cloak>\n");
return startOpenVPN(ovpnConfig);
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
}
bool IosController::setupWireGuard()
{
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
QJsonDocument doc(m_rawConfig);
QString wgConfig(doc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfig);
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::wireguard::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
}
bool IosController::setupAwg()
{
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
QJsonDocument doc(m_rawConfig);
QString wgConfig(doc.toJson(QJsonDocument::Compact));
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
return startWireGuard(wgConfig);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::awg::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);
wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]);
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
}
bool IosController::startOpenVPN(const QString &config)
@@ -448,23 +579,17 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel()
{
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0;
m_txBytes = 0;
int STT = m_rawConfig["splitTunnelType"].toInt();
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
QJsonDocument doc;
doc.setArray(splitTunnelSites);
QString STS(doc.toJson());
[m_currentTunnel setEnabled:YES];
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
@@ -485,23 +610,6 @@ void IosController::startTunnel()
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() : @"";
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
NSString *SplitTunnelSitesValue = STS.toNSString();
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
sendVpnExtensionMessage(message);
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) {
@@ -516,7 +624,6 @@ void IosController::startTunnel()
}];
}
bool IosController::isOurManager(NETunnelProviderManager* manager) {
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
@@ -578,7 +685,7 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
NSError *sendError = nil;
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else {
@@ -21,7 +21,7 @@
}
- (void) vpnConfigurationDidChange:(NSNotification *)notification {
cppController->vpnStatusDidChange(notification);
// cppController->vpnStatusDidChange(notification);
}
@@ -16,9 +16,6 @@
#include "leakdetector.h"
#include "logger.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsLinux");
}
@@ -38,8 +35,6 @@ bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
}
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config);
// Create socket file descriptor to perform the ioctl operations on
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) {
@@ -56,10 +51,10 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
// FIXME: We need to know how many layers deep this particular
// interface is into a tunnel to work effectively. Otherwise
// we will run into fragmentation issues.
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU -- Return code: " << ret;
logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false;
}
@@ -19,8 +19,10 @@
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "../utilities.h"
#include "leakdetector.h"
#include "logger.h"
#include "core/networkUtilities.h"
namespace {
Logger logger("LinuxRouteMonitor");
@@ -163,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4;
inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4);
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;
@@ -221,122 +223,6 @@ void LinuxRouteMonitor::nlsockReady() {
}
}
#define BUFFER_SIZE 4096
QString LinuxRouteMonitor::getgatewayandiface()
{
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *)msgbuf;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
}
/* receive response */
do
{
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
}
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
{
switch(route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
break;
}
}
if ((*gateway_address) && (*interface)) {
logger.debug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
}
close(sock);
return gateway_address;
}
static bool buildAllowedIp(wg_allowedip* ip,
const IPAddress& prefix) {
const char* addrString = qPrintable(prefix.address().toString());
@@ -31,7 +31,6 @@ class LinuxRouteMonitor final : public QObject {
static QString addrToString(const QByteArray& data);
bool rtmSendRoute(int action, int flags, int type,
const IPAddress& prefix);
QString getgatewayandiface();
QString m_ifname;
unsigned int m_ifindex = 0;
int m_nlsock = -1;
@@ -20,9 +20,6 @@
#include "logger.h"
#include "macosdaemon.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsMacos");
}
@@ -56,10 +53,10 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
// MTU
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU:" << strerror(errno);
logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false;
}
@@ -18,6 +18,7 @@
#include "dnsutilswindows.h"
#include "leakdetector.h"
#include "logger.h"
#include "core/networkUtilities.h"
#include "platforms/windows/windowscommons.h"
#include "platforms/windows/windowsservicemanager.h"
#include "windowsfirewall.h"
@@ -43,11 +44,24 @@ WindowsDaemon::~WindowsDaemon() {
logger.debug() << "Daemon released";
}
void WindowsDaemon::prepareActivation(const InterfaceConfig& config) {
void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAdapterIndex) {
// Before creating the interface we need to check which adapter
// routes to the server endpoint
auto serveraddr = QHostAddress(config.m_serverIpv4AddrIn);
m_inetAdapterIndex = WindowsCommons::AdapterIndexTo(serveraddr);
if (inetAdapterIndex == 0) {
auto serveraddr = QHostAddress(config.m_serverIpv4AddrIn);
m_inetAdapterIndex = NetworkUtilities::AdapterIndexTo(serveraddr);
} else {
m_inetAdapterIndex = inetAdapterIndex;
}
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager.stop();
}
}
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
@@ -64,12 +78,8 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
}
}
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager.start(m_inetAdapterIndex);
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager.stop();
}
activateSplitTunnel(config);
return true;
}
@@ -20,7 +20,8 @@ class WindowsDaemon final : public Daemon {
WindowsDaemon();
~WindowsDaemon();
void prepareActivation(const InterfaceConfig& config) override;
void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) override;
void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) override;
protected:
bool run(Op op, const InterfaceConfig& config) override;

Some files were not shown because too many files have changed in this diff Show More