Compare commits

...

51 Commits

Author SHA1 Message Date
cd-amn 34d0894666 fix: keep VPN gateway for OpenVPN routes on macOS/Windows to restore blacklist split tunneling 2026-06-23 21:33:13 +04:00
cd-amn ccf43297b0 fix: apply OpenVPN/IKEv2 DNS resolvers via TrafficGuard on Linux 2026-06-23 15:32:29 +04:00
cd-amn 4543c5b534 refactor: route OpenVPN and IKEv2 through Tunnel/TrafficGuard 2026-06-22 19:22:50 +04:00
cd-amn cd382546ac fix: keep Xray catch-all routes during seamless switch on macOS 2026-06-19 16:22:19 +00:00
cd-amn 8c5b4781d5 fix: keep DNS and routing during seamless Xray switch on Linux 2026-06-18 19:45:12 +04:00
cd-amn 8da439f0b3 fix: pick physical uplink for Xray during seamless switch on macOS 2026-06-18 14:45:58 +04:00
cd-amn e9cd043b10 fix: keep DNS and routing during seamless Xray switch on macOS 2026-06-18 14:45:58 +04:00
cd-amn eda9ed8016 fix: allocate utun-pattern interface names on macOS 2026-06-18 14:45:58 +04:00
cd-amn 225b693ea4 fix: keep VPN routing on macOS when switching between WG/AWG tunnels 2026-06-18 14:45:58 +04:00
cd-amn 97b2de8cd1 refactor: TrafficGuard owns adapter IP swap for WG/AWG and Xray 2026-06-18 14:45:58 +04:00
cd-amn ef1ed7064a feat: TrafficGuard owns Xray adapter IP swap on Windows 2026-06-18 14:45:58 +04:00
cd-amn 40ba31f54b fix: dedup RouterLinux route tracking and tolerate EEXIST/ESRCH 2026-06-18 14:45:58 +04:00
cd-amn 7bb609866a fix: close traffic leak during seamless tunnel switch 2026-06-18 14:45:58 +04:00
cd-amn 54d28862f3 refactor: TrafficGuard owns xray DNS and uplink routes 2026-06-18 14:45:58 +04:00
cd-amn bff3e228fc feat: route Xray through Tunnel for seamless server switch 2026-06-18 14:45:58 +04:00
cd-amn d528a241d8 feat: key xray worker per tunnel via ifname-scoped IPC 2026-06-18 14:45:58 +04:00
cd-amn 99e6c18f15 feat: run xray-core in a forked worker process 2026-06-18 14:45:58 +04:00
cd-amn 6d49a9416e fix: propagate tun2socks FailedToStart in XrayProtocol 2026-06-18 14:45:58 +04:00
cd-amn 80fa788802 refactor: prepare XrayProtocol for Tunnel two-phase lifecycle 2026-06-18 14:45:58 +04:00
cd-amn 3590b2d323 feat: seamless WG switch on Windows for shared client IPs 2026-06-18 14:45:58 +04:00
cd-amn 9a1e380ffb feat: per-tunnel Windows firewall for seamless WG switch 2026-06-18 14:45:58 +04:00
cd-amn eb42ce8fef refactor: route Windows WG killswitch through TrafficGuard 2026-06-18 14:45:58 +04:00
cd-amn 72147d3a67 feat: per-tunnel ifname for WG service, UAPI pipe, and SCM for Windows 2026-06-18 14:45:58 +04:00
cd-amn 39f9bcfd50 fix: revert API country selection on server switch failure 2026-06-18 14:45:58 +04:00
cd-amn b6188baeb8 fix: traffic drops for killswitch blacklisted sites during the switch 2026-06-18 14:45:58 +04:00
cd-amn 9b329ad5b1 refactor: move routing/KS/DNS lifecycle from Daemon to TrafficGuard 2026-06-18 14:45:58 +04:00
cd-amn ce05b4e99c refactor: internalize AMNEZIA_DESKTOP guard in appendKillSwitchConfig 2026-06-18 14:45:58 +04:00
cd-amn 75f522e9dc chore: remove unused VpnConnection::m_routeMode field 2026-06-18 14:45:58 +04:00
cd-amn d6349b5734 fix: remove duplicate routeAddList in setupRoutes 2026-06-18 14:45:58 +04:00
cd-amn fa8014093b feat: drive WG via Tunnel coordinator for seamless server switch 2026-06-18 14:45:57 +04:00
cd-amn 9d69ab89d5 feat: introduce Tunnel wrapping VpnProtocol with two-phase lifecycle 2026-06-18 14:33:36 +04:00
cd-amn f2ff8a7b3b refactor: thread interface name through LocalSocketController 2026-06-18 14:33:36 +04:00
cd-amn 864b8c6f8a feat: split daemon activation into bare bring-up and setPrimary 2026-06-18 14:33:36 +04:00
cd-amn adb8eb4937 refactor: own killswitch teardown at daemon level 2026-06-18 14:33:36 +04:00
cd-amn 6750afd330 refactor: cache physical gateway and use kernel-assigned netlink pid 2026-06-18 14:33:36 +04:00
cd-amn 7ad0692306 feat: enable base Daemon to create and swap WireguardUtils without knowing platform type 2026-06-18 14:33:36 +04:00
cd-amn 0dcd05c6c3 feat: use per-tunnel ifname instead of hardcoded WG_INTERFACE in platform helpers 2026-06-18 14:33:36 +04:00
cd-amn 83e82c16a7 feat: decouple TUN name from XrayProtocol to support dual tunnels 2026-06-18 14:33:36 +04:00
cd-amn f29b6cf027 feat: let callers specify WireGuard interface name per tunnel 2026-06-18 14:33:36 +04:00
cd-amn 4bca2df4a2 feat: revoke old endpoint from KS allowlist after server switch 2026-06-18 14:33:36 +04:00
cd-amn f67927667a feat: killswitch/routing teardown only on explicit protocol stop 2026-06-18 14:33:36 +04:00
cd-amn cc469e74ed fix: deleteRoutePrefix incorrectly called insertRoute for specific prefixes 2026-06-18 14:33:36 +04:00
NickVs2015 234c70f495 fix: NM down/up reconnection problem 2026-06-18 14:33:36 +04:00
cd-amn 850b698e83 fix: add missing Qt includes to updateController 2026-06-18 14:33:36 +04:00
cd-amn 42570c54f8 feat: decouple routing/killswitch from protocol handling 2026-06-18 14:33:35 +04:00
Yaroslav Gurov 890103a16a fix: update amneziawg (#2743)
* chore(conan): update amneziawg

* fix(conan): use cmake 4.2+ to support MSVC26

* fix(ci/cd): use the latest cmake generator available on windows
2026-06-17 19:56:53 +07:00
yp 56ab82f87f fix: Use shared OpenSSL on Android (#2736) 2026-06-16 10:57:32 +07:00
lunardunno 3984acbb44 feat: updating install_docker.sh script (#2661)
* Updating install_docker.sh script

Implementing a Docker service status check.
The Docker reinstall step has been removed due to the implementation of Docker service checking.
Implementing locale checking and assignment.
Implementation of execution of some actions through commands with sudo, to reduce delays caused by differences in the values ​​of the PATH variable for the root user and the user included in the sudo group.
Implementation of a verification step for the install containerization app to avoid installing unsupported podman-docker applications.

* adding message handling to install controller

Adding handling for "Containerization app is not supported" and "Service status not active" messages to the controller.

* Error Codes added

Error Codes added for ServerContainerizationNotSupported & DockerServiceNotActive

* Adding extended descriptions of new errors

* fix last line in errorCodes.h

* fix last line in errorStrings.cpp

* Changing the names of errors

* various changes in the script

The messages output for processing by the server controller have been changed: "Container runtime is not supported" and "Container runtime service is not running."
The redundant check and output of the "Packet manager not found" message, as well as the interruption of script execution, have been eliminated, as this situation is handled by the server controller at an earlier stage (check_server_is_busy.sh) and only there.
Added installation of the whish package if it is missing from the OS, for subsequent re-execution of the install_docker.sh and check_server_is_busy.sh scripts.
Implemented an alternative method for detecting the package manager if the whish package is initially missing from the OS.
The algorithm for setting the $pm variable (package manager) has been changed.

* processed phrases have been changed

The phrases processed by the server controller have been changed.

* Attempting to use "command -v"

Switching to using "command -v" instead of "which".

* "which" as main, "command" as backup.

* "which" as main, "command" as backup for check user

* which  LOCK_CMD with sudo

Run the "which" with sudo to check the $LOCK_CMD variable in case the user's PATH variable has incorrect values ​​if the user is not root and is only a member of the sudo group.

* suppressing sudo password prompt

* suppressing sudo password prompt

* suppressing sudo password prompt install_docker.sh

* Changing the phrase for check stdout

"sudo:" with "not found" instead of "command not found"

* Changing phrases for check stdout check_user_in_sudo.sh‎

* sudo|docker and not found, in one line

* check only sudoers
2026-06-15 22:28:38 +07:00
yp cc404378f9 fix: remove only amnezia- prefixed docker volumes (#2728) 2026-06-15 13:12:19 +07:00
yp 594635e5cf fix: script remove docker volume (#2686)
* move sudo docker volume rm -f

* fix: remove unnecessary function

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-04 22:58:39 +08:00
vkamn f9b106cf5b fix: various fixes (#2693)
* fix: fixed country model update

* fix: fixed context menu crush on ios

* fix: fixed passphrase dialog freeze

* fix: fixed country switch

* fix: fixed start minimized

* fix: fixed black screen after remove container

* refactor: return cloak and ss only for view

* fix: fixed default server change after improt while connected

* fix: divider visibility

* fix: fixed revoke admin user

* fix: fixed language restore after backup

* fix: link hover for tor settings page

* fix: fixed openvpn connecntion status

* fix: fixed free color status

* fix: fixed client config update

* chore: bump version
2026-06-04 22:45:53 +08:00
148 changed files with 3405 additions and 1458 deletions
+1 -1
View File
@@ -157,7 +157,7 @@ jobs:
run: pip install "conan==2.28.0" run: pip install "conan==2.28.0"
- name: 'Build dependencies' - name: 'Build dependencies'
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1 run: cmake -S . -B build -DPREBUILTS_ONLY=1
- name: 'Authorize in remote' - name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
+2 -2
View File
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.1) set(AMNEZIAVPN_VERSION 4.9.0.2)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE) set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2122) set(APP_ANDROID_VERSION_CODE 2123)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+4
View File
@@ -65,6 +65,8 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/utilities.h ${CLIENT_ROOT_DIR}/core/utils/utilities.h
${CLIENT_ROOT_DIR}/core/utils/managementServer.h ${CLIENT_ROOT_DIR}/core/utils/managementServer.h
${CLIENT_ROOT_DIR}/core/utils/constants.h ${CLIENT_ROOT_DIR}/core/utils/constants.h
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h
${CLIENT_ROOT_DIR}/core/tunnel.h
) )
# Mozilla headres # Mozilla headres
@@ -145,6 +147,8 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp ${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp ${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp ${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp
${CLIENT_ROOT_DIR}/core/tunnel.cpp
) )
# Mozilla sources # Mozilla sources
@@ -484,6 +484,12 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
return ErrorCode::NoError; return ErrorCode::NoError;
} }
void SubscriptionController::restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config)
{
const QJsonObject json = config.toJson();
m_serversRepository->editServer(serverId, json, serverConfigUtils::configTypeFromJson(json));
}
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId) ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
{ {
auto apiV2 = m_serversRepository->apiV2Config(serverId); auto apiV2 = m_serversRepository->apiV2Config(serverId);
@@ -68,6 +68,8 @@ public:
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent); ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
void restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config);
ErrorCode deactivateDevice(const QString &serverId); ErrorCode deactivateDevice(const QString &serverId);
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode); ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
@@ -28,6 +28,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
m_vpnConnection(vpnConnection) m_vpnConnection(vpnConnection)
{ {
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged); connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
connect(m_vpnConnection, &VpnConnection::serverSwitchFailed, this, &ConnectionController::serverSwitchFailed);
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection); connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
@@ -49,14 +50,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
} }
} }
ErrorCode ConnectionController::prepareConnection(const QString &serverId, ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
QJsonObject& vpnConfiguration,
DockerContainer& container)
{ {
const auto kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
return ErrorCode::LegacyApiV1NotSupportedError;
case serverConfigUtils::ConfigType::Invalid:
default:
return ErrorCode::InternalError;
}
}
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
{
if (serverId.isEmpty()) {
return ErrorCode::InternalError;
}
if (!isServiceReady()) { if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning; return ErrorCode::AmneziaServiceNotRunning;
} }
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
return ErrorCode::LegacyApiV1NotSupportedError;
}
DockerContainer container = DockerContainer::None;
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (container == DockerContainer::None) {
return ErrorCode::NoInstalledContainersError;
}
if (ContainerUtils::isUnsupportedContainer(container)) {
return ErrorCode::LegacyContainerNotSupportedError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
return ErrorCode::NoError;
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
ContainerConfig containerConfigModel; ContainerConfig containerConfigModel;
QPair<QString, QString> dns; QPair<QString, QString> dns;
QString hostName; QString hostName;
@@ -120,10 +199,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
return ErrorCode::InternalError; return ErrorCode::InternalError;
} }
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion, vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container); containerConfigModel, container);
@@ -34,6 +34,8 @@ public:
QJsonObject& vpnConfiguration, QJsonObject& vpnConfiguration,
DockerContainer& container); DockerContainer& container);
ErrorCode isConnectionSupported(const QString &serverId) const;
ErrorCode openConnection(const QString &serverId); ErrorCode openConnection(const QString &serverId);
void closeConnection(); void closeConnection();
@@ -67,12 +69,15 @@ signals:
void closeConnectionRequested(); void closeConnectionRequested();
void setConnectionStateRequested(Vpn::ConnectionState state); void setConnectionStateRequested(Vpn::ConnectionState state);
void killSwitchModeChangedRequested(bool enabled); void killSwitchModeChangedRequested(bool enabled);
void serverSwitchFailed();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
void restoreConnectionRequested(); void restoreConnectionRequested();
#endif #endif
private: private:
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
SecureServersRepository* m_serversRepository; SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository; SecureAppSettingsRepository* m_appSettingsRepository;
VpnConnection* m_vpnConnection; VpnConnection* m_vpnConnection;
+1 -1
View File
@@ -191,7 +191,7 @@ void CoreController::initControllers()
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this); m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
setQmlContextProperty("LanguageUiController", m_languageUiController); setQmlContextProperty("LanguageUiController", m_languageUiController);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this); m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
setQmlContextProperty("SettingsController", m_settingsUiController); setQmlContextProperty("SettingsController", m_settingsUiController);
m_pageController = new PageController(m_serversController, m_settingsController, this); m_pageController = new PageController(m_serversController, m_settingsController, this);
+22 -12
View File
@@ -33,7 +33,6 @@
#include "core/controllers/connectionController.h" #include "core/controllers/connectionController.h"
#include "ui/models/clientManagementModel.h" #include "ui/models/clientManagementModel.h"
#include "ui/controllers/api/apiNewsUiController.h" #include "ui/controllers/api/apiNewsUiController.h"
#include "ui/models/api/apiCountryModel.h"
#include "ui/models/containersModel.h" #include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h" #include "core/utils/containerEnum.h"
@@ -95,6 +94,12 @@ void CoreSignalHandlers::initErrorMessagesHandler()
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected); m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
}); });
connect(m_coreController->m_connectionUiController, &ConnectionUiController::serverSwitchFailed, this, [this]() {
m_coreController->m_subscriptionUiController->revertLastCountryChange();
emit m_coreController->m_pageController->showNotificationMessage(
tr("Failed to switch server. Existing connection maintained."));
});
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController, connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
qOverload<ErrorCode>(&PageController::showErrorMessage)); qOverload<ErrorCode>(&PageController::showErrorMessage));
@@ -156,8 +161,11 @@ void CoreSignalHandlers::initExportControllerHandler()
void CoreSignalHandlers::initImportControllerHandler() void CoreSignalHandlers::initImportControllerHandler()
{ {
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() { connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (!m_coreController->m_connectionController->isConnected()) { if (m_coreController->m_connectionUiController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1; return;
}
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex); const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) { if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId); m_coreController->m_serversController->setDefaultServer(serverId);
@@ -165,7 +173,6 @@ void CoreSignalHandlers::initImportControllerHandler()
if (m_coreController->m_serversUiController) { if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId); m_coreController->m_serversUiController->setProcessedServerId(serverId);
} }
}
}); });
} }
@@ -177,16 +184,13 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
return; return;
} }
QJsonArray availableCountries;
QString serverCountryCode;
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId); const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
if (apiV2.has_value()) { if (!apiV2.has_value()) {
availableCountries = apiV2->apiConfig.availableCountries; return;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
} }
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode); m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
apiV2->apiConfig.serverCountryCode);
}); });
} }
@@ -237,13 +241,16 @@ void CoreSignalHandlers::initLanguageHandler()
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() { connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum()); m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
}); });
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
});
} }
void CoreSignalHandlers::initAutoConnectHandler() void CoreSignalHandlers::initAutoConnectHandler()
{ {
if (m_coreController->m_settingsUiController->isAutoConnectEnabled() if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) { && !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); }); QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
} }
} }
@@ -348,6 +355,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
{ {
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested, connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested); m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
} }
void CoreSignalHandlers::initStrictKillSwitchHandler() void CoreSignalHandlers::initStrictKillSwitchHandler()
@@ -79,7 +79,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
QString ip = NetworkUtilities::getIPAddress(host); QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) { if (!ip.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) { IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip }); QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QString(), QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue()) if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call"; qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
}); });
@@ -72,6 +72,16 @@ namespace
} }
return false; return false;
} }
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
{
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
if (removeDataVolume) {
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
script = SshSession::replaceVars(script, vars);
}
return script;
}
} }
InstallController::InstallController(SecureServersRepository *serversRepository, InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e; return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished"; qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
amnezia::ScriptVars removeContainerVars = const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString()); amnezia::genBaseVars(credentials, container, QString(), QString());
if (!isUpdate) { const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } }); sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
}
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
removeContainerVars));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished"; qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start"; qDebug().noquote() << "buildContainerWorker start";
@@ -152,7 +158,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return startupContainerWorker(credentials, container, config, sshSession); return startupContainerWorker(credentials, container, config, sshSession);
} }
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig) ContainerConfig &newConfig)
{ {
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) { if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
@@ -185,7 +191,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
SshSession sshSession(this); SshSession sshSession(this);
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
bool xrayServerSettingsChanged = false; bool xrayServerSettingsChanged = false;
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) { if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
@@ -213,11 +219,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) { if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() }; DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession); XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall=" qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
<< reinstallRequired; << reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false); errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error=" qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
<< static_cast<int>(errorCode); << static_cast<int>(errorCode);
} }
} }
@@ -236,6 +242,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
return errorCode; return errorCode;
} }
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto config = m_serversRepository->selfHostedUserConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
auto config = m_serversRepository->nativeConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
return ErrorCode::NoError;
}
default:
return ErrorCode::InternalError;
}
}
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container) void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
{ {
if (ContainerUtils::containerService(container) == ServiceType::Other) { if (ContainerUtils::containerService(container) == ServiceType::Other) {
@@ -795,8 +836,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut; qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) { if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)"); QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut); QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
if (match.hasMatch()) { if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt(); int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt(); int minorVersion = match.captured(2).toInt();
@@ -809,8 +850,19 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock")) if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError; return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found")) if (stdOut.contains("Container runtime is not supported"))
return ErrorCode::ServerContainerRuntimeNotSupported;
QRegularExpression notFoundRegex(
R"(^.*(?:sudo:|docker:).*not found.*$)",
QRegularExpression::MultilineOption);
if (notFoundRegex.match(stdOut).hasMatch()) {
return ErrorCode::ServerDockerFailedError; return ErrorCode::ServerDockerFailedError;
}
if (stdOut.contains("Container runtime service not running"))
return ErrorCode::ContainerRuntimeServiceNotRunning;
return error; return error;
} }
@@ -847,7 +899,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserNotInSudo; return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory")) if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible; return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on")) if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
return ErrorCode::ServerUserNotAllowedInSudoers; return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required")) if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired; return ErrorCode::ServerUserPasswordRequired;
@@ -980,12 +1032,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError; return ErrorCode::InternalError;
} }
SshSession sshSession(this); SshSession sshSession(this);
amnezia::ScriptVars removeContainerVars = const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString()); amnezia::genBaseVars(credentials, container, QString(), QString());
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } }); const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
ErrorCode errorCode = sshSession.runScript( ErrorCode errorCode =
credentials, sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers; QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
@@ -1463,7 +1514,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = containerAndPortMatch.captured(3); QString transportProtoStr = containerAndPortMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name); DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) { if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue; continue;
} }
@@ -1488,7 +1539,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = torOrDnsRegMatch.captured(3); QString transportProtoStr = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name); DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) { if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue; continue;
} }
@@ -34,7 +34,12 @@ public:
~InstallController(); ~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
ErrorCode rebootServer(const QString &serverId); ErrorCode rebootServer(const QString &serverId);
ErrorCode removeAllContainers(const QString &serverId); ErrorCode removeAllContainers(const QString &serverId);
@@ -7,6 +7,8 @@
#include <QJsonObject> #include <QJsonObject>
#include <QSysInfo> #include <QSysInfo>
#include <QTimer> #include <QTimer>
#include <QStandardPaths>
#include <QTemporaryDir>
#include "amneziaApplication.h" #include "amneziaApplication.h"
#include "logger.h" #include "logger.h"
@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
return containers.value(container); return containers.value(container);
} }
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{ {
QString d1 = dns1; QString d1 = dns1;
@@ -27,6 +27,8 @@ struct NativeServerConfig {
bool hasContainers() const; bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const; ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const; QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const; QJsonObject toJson() const;
@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
return containers.value(container); return containers.value(container);
} }
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns, QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
const QString &secondaryDns) const const QString &secondaryDns) const
{ {
@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
bool hasContainers() const; bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const; ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const; QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const; QJsonObject toJson() const;
+22 -63
View File
@@ -39,33 +39,35 @@ QString OpenVpnProtocol::defaultConfigPath()
return p; return p;
} }
void OpenVpnProtocol::stop() void OpenVpnProtocol::cleanupResources()
{ {
qDebug() << "OpenVpnProtocol::stop()"; if (m_openVpnProcess || openVpnProcessIsRunning()) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
// TODO: need refactoring
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|| (m_connectionState == Vpn::ConnectionState::Connected)
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
if (!sendTermSignal()) { if (!sendTermSignal()) {
killOpenVpnProcess(); killOpenVpnProcess();
} }
QThread::msleep(10); QThread::msleep(10);
}
m_managementServer.stop(); m_managementServer.stop();
}
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|| m_connectionState == Vpn::ConnectionState::Connecting
|| m_connectionState == Vpn::ConnectionState::Connected
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
if (wasActive) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
} }
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) cleanupResources();
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
#endif
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
}
} }
ErrorCode OpenVpnProtocol::prepare() ErrorCode OpenVpnProtocol::prepare()
@@ -168,27 +170,13 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
ErrorCode OpenVpnProtocol::start() ErrorCode OpenVpnProtocol::start()
{ {
OpenVpnProtocol::stop(); cleanupResources();
if (!QFileInfo::exists(configPath())) { if (!QFileInfo::exists(configPath())) {
setLastError(ErrorCode::OpenVpnConfigMissing); setLastError(ErrorCode::OpenVpnConfigMissing);
return lastError(); return lastError();
} }
#ifdef AMNEZIA_DESKTOP
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString());
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
return ErrorCode::NoError;
});
if (res != ErrorCode::NoError) {
return res;
}
#endif
// Detect default gateway // Detect default gateway
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
QProcess p; QProcess p;
@@ -343,38 +331,9 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QThread::msleep(300); QThread::msleep(300);
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
iface->enablePeerTraffic(m_configData);
}
}
}
});
#endif #endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
}
});
}
#endif #endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
} }
+1
View File
@@ -29,6 +29,7 @@ protected slots:
void onReadyReadDataFromManagementServer(); void onReadyReadDataFromManagementServer();
private: private:
void cleanupResources();
QString configPath() const; QString configPath() const;
bool openVpnProcessIsRunning() const; bool openVpnProcessIsRunning() const;
bool sendTermSignal(); bool sendTermSignal();
+22
View File
@@ -106,6 +106,19 @@ QString VpnProtocol::vpnLocalAddress() const
return m_vpnLocalAddress; return m_vpnLocalAddress;
} }
bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container)
{
return container == amnezia::DockerContainer::Awg
|| container == amnezia::DockerContainer::Awg2
|| container == amnezia::DockerContainer::WireGuard;
}
bool VpnProtocol::isXrayBased(amnezia::DockerContainer container)
{
return container == amnezia::DockerContainer::Xray
|| container == amnezia::DockerContainer::SSXray;
}
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{ {
switch (container) { switch (container) {
@@ -124,6 +137,14 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
} }
} }
void VpnProtocol::setPrimary(const QJsonObject &config)
{
Q_UNUSED(config)
QMetaObject::invokeMethod(this, [this]() {
emit primaryReady();
}, Qt::QueuedConnection);
}
QString VpnProtocol::routeGateway() const QString VpnProtocol::routeGateway() const
{ {
return m_routeGateway; return m_routeGateway;
@@ -137,6 +158,7 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState)
case Vpn::ConnectionState::Preparing: return tr("Preparing"); case Vpn::ConnectionState::Preparing: return tr("Preparing");
case Vpn::ConnectionState::Connecting: return tr("Connecting..."); case Vpn::ConnectionState::Connecting: return tr("Connecting...");
case Vpn::ConnectionState::Connected: return tr("Connected"); case Vpn::ConnectionState::Connected: return tr("Connected");
case Vpn::ConnectionState::Switching: return tr("Switching...");
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
case Vpn::ConnectionState::Error: return tr("Error"); case Vpn::ConnectionState::Error: return tr("Error");
+6
View File
@@ -27,6 +27,7 @@ namespace Vpn
Preparing, Preparing,
Connecting, Connecting,
Connected, Connected,
Switching,
Disconnecting, Disconnecting,
Reconnecting, Reconnecting,
Error Error
@@ -60,6 +61,7 @@ public:
virtual bool isDisconnected() const; virtual bool isDisconnected() const;
virtual ErrorCode start() = 0; virtual ErrorCode start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual void setPrimary(const QJsonObject& config);
Vpn::ConnectionState connectionState() const; Vpn::ConnectionState connectionState() const;
ErrorCode lastError() const; ErrorCode lastError() const;
@@ -71,6 +73,8 @@ public:
QString vpnLocalAddress() const; QString vpnLocalAddress() const;
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
static bool isWireGuardBased(amnezia::DockerContainer container);
static bool isXrayBased(amnezia::DockerContainer container);
signals: signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -78,6 +82,8 @@ signals:
void timeoutTimerEvent(); void timeoutTimerEvent();
void protocolError(amnezia::ErrorCode e); void protocolError(amnezia::ErrorCode e);
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress); void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
void primaryReady();
void primaryFailed();
public slots: public slots:
virtual void onTimeout(); // todo: remove? virtual void onTimeout(); // todo: remove?
+17 -14
View File
@@ -12,9 +12,11 @@
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent) : VpnProtocol(configuration, parent)
{ {
m_impl.reset(new LocalSocketController()); const QString ifname = configuration.value("ifname").toString();
m_impl.reset(new LocalSocketController(ifname));
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString& pubkey, const QDateTime&) {
Q_UNUSED(pubkey)
setConnectionState(Vpn::ConnectionState::Connected); setConnectionState(Vpn::ConnectionState::Connected);
}); });
connect(m_impl.get(), &ControllerImpl::statusUpdated, this, connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
@@ -33,12 +35,18 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) || if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) { (!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
if (m_connectionState == Vpn::ConnectionState::Connected) {
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress); emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
} }
}
}); });
connect(m_impl.get(), &ControllerImpl::disconnected, this, connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); [this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
connect(m_impl.get(), &ControllerImpl::primaryReady,
this, &WireguardProtocol::primaryReady);
connect(m_impl.get(), &ControllerImpl::primaryFailed,
this, &WireguardProtocol::primaryFailed);
m_impl->initialize(nullptr, nullptr); m_impl->initialize(nullptr, nullptr);
} }
@@ -48,13 +56,7 @@ WireguardProtocol::~WireguardProtocol()
QThread::msleep(200); QThread::msleep(200);
} }
void WireguardProtocol::stop() ErrorCode WireguardProtocol::start()
{
stopMzImpl();
return;
}
ErrorCode WireguardProtocol::startMzImpl()
{ {
QString protocolName = m_rawConfig.value("protocol").toString(); QString protocolName = m_rawConfig.value("protocol").toString();
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
@@ -62,18 +64,19 @@ ErrorCode WireguardProtocol::startMzImpl()
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData); m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString()); m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
m_stopped = false;
m_impl->activate(m_rawConfig); m_impl->activate(m_rawConfig);
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode WireguardProtocol::stopMzImpl() void WireguardProtocol::stop()
{ {
if (m_stopped) return;
m_stopped = true;
m_impl->deactivate(); m_impl->deactivate();
return ErrorCode::NoError;
} }
void WireguardProtocol::setPrimary(const QJsonObject& config)
ErrorCode WireguardProtocol::start()
{ {
return startMzImpl(); m_impl->setPrimary(config);
} }
+2 -3
View File
@@ -21,11 +21,10 @@ public:
ErrorCode start() override; ErrorCode start() override;
void stop() override; void stop() override;
void setPrimary(const QJsonObject& config) override;
ErrorCode startMzImpl();
ErrorCode stopMzImpl();
private: private:
bool m_stopped = false;
QScopedPointer<ControllerImpl> m_impl; QScopedPointer<ControllerImpl> m_impl;
}; };
+52 -96
View File
@@ -19,12 +19,6 @@
#include <exception> #include <exception>
#ifdef Q_OS_MACOS
static const QString tunName = "utun22";
#else
static const QString tunName = "tun2";
#endif
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{ {
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr; m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
@@ -34,11 +28,16 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt()); m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString()); m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString(); m_tunName = configuration.value("tunName").toString();
m_dnsServers.push_back(QHostAddress(primaryDns)); if (m_tunName.isEmpty()) {
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) { m_tunName = configuration.value("ifname").toString();
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString(); }
m_dnsServers.push_back(QHostAddress(secondaryDns)); if (m_tunName.isEmpty()) {
#ifdef Q_OS_MACOS
m_tunName = QStringLiteral("utun22");
#else
m_tunName = QStringLiteral("tun2");
#endif
} }
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject(); QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
@@ -68,6 +67,8 @@ ErrorCode XrayProtocol::start()
{ {
qDebug() << "XrayProtocol::start()"; qDebug() << "XrayProtocol::start()";
m_phase = Phase::Active;
// Inject SOCKS5 auth into the inbound before starting xray. // Inject SOCKS5 auth into the inbound before starting xray.
// Re-uses existing credentials if the config already has them (e.g. imported config). // Re-uses existing credentials if the config already has them (e.g. imported config).
amnezia::serialization::inbounds::InboundCredentials creds; amnezia::serialization::inbounds::InboundCredentials creds;
@@ -106,7 +107,7 @@ ErrorCode XrayProtocol::start()
return IpcClient::withInterface( return IpcClient::withInterface(
[&](QSharedPointer<IpcInterfaceReplica> iface) { [&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(xrayConfigStr); auto xrayStart = iface->xrayStart(m_tunName, xrayConfigStr);
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) { if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray"; qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed; return ErrorCode::XrayExecutableCrashed;
@@ -120,24 +121,17 @@ void XrayProtocol::stop()
{ {
qDebug() << "XrayProtocol::stop()"; qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) { if (m_phase != Phase::Active) {
auto disableKillSwitch = iface->disableKillSwitch(); return;
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue()) }
qWarning() << "Failed to disable killswitch"; m_phase = Phase::Stopping;
auto StartRoutingIpv6 = iface->StartRoutingIpv6(); IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) auto deleteTun = iface->deleteTun(m_tunName);
qWarning() << "Failed to start routing ipv6";
auto restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
qWarning() << "Failed to restore resolvers";
auto deleteTun = iface->deleteTun(tunName);
if (!deleteTun.waitForFinished() || !deleteTun.returnValue()) if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
qWarning() << "Failed to delete tun"; qWarning() << "Failed to delete tun";
auto xrayStop = iface->xrayStop(); auto xrayStop = iface->xrayStop(m_tunName);
if (!xrayStop.waitForFinished() || !xrayStop.returnValue()) if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray"; qWarning() << "Failed to stop xray";
}); });
@@ -162,9 +156,18 @@ void XrayProtocol::stop()
m_tun2socksProcess.reset(); m_tun2socksProcess.reset();
} }
m_phase = Phase::Inactive;
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
} }
void XrayProtocol::setPrimary(const QJsonObject &config)
{
Q_UNUSED(config)
QMetaObject::invokeMethod(this, [this]() {
emit primaryReady();
}, Qt::QueuedConnection);
}
ErrorCode XrayProtocol::startTun2Socks() ErrorCode XrayProtocol::startTun2Socks()
{ {
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess(); m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
@@ -175,7 +178,20 @@ ErrorCode XrayProtocol::startTun2Socks()
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort)); const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks); m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl }); m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(m_tunName), "-proxy", proxyUrl });
connect(
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::errorOccurred, this,
[this](QProcess::ProcessError error) {
if (error != QProcess::FailedToStart) {
// Other errors are reported via the finished signal or are transient.
return;
}
qCritical() << "Tun2socks failed to start";
stop();
setLastError(ErrorCode::Tun2SockExecutableMissing);
},
Qt::QueuedConnection);
connect( connect(
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this, m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
@@ -217,13 +233,17 @@ ErrorCode XrayProtocol::startTun2Socks()
} }
} }
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) { if (m_phase == Phase::Active && resourceBusy
&& m_tun2socksRetryCount < maxTun2SocksRetries) {
m_tun2socksRetryCount++; m_tun2socksRetryCount++;
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...") qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
.arg(m_tun2socksRetryCount) .arg(m_tun2socksRetryCount)
.arg(maxTun2SocksRetries) .arg(maxTun2SocksRetries)
.arg(tun2socksRetryDelayMs); .arg(tun2socksRetryDelayMs);
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() { QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
if (m_phase != Phase::Active) {
return;
}
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) { if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
stop(); stop();
setLastError(err); setLastError(err);
@@ -252,81 +272,17 @@ ErrorCode XrayProtocol::setupRouting()
{ {
return IpcClient::withInterface( return IpcClient::withInterface(
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode { [this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
#ifdef Q_OS_WIN #ifndef Q_OS_WIN
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress)); auto createTun = iface->createTun(m_tunName, amnezia::protocols::xray::defaultLocalAddr);
#endif
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
if (!createTun.waitForFinished() || !createTun.returnValue()) { if (!createTun.waitForFinished() || !createTun.returnValue()) {
qCritical() << "Failed to assign IP address for TUN"; qCritical() << "Failed to assign IP address for TUN";
return ErrorCode::InternalError; return ErrorCode::InternalError;
} }
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
qCritical() << "Failed to set DNS resolvers for TUN";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
int vpnAdapterIndex = -1;
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (auto &netInterface : netInterfaces) {
for (auto &address : netInterface.addressEntries()) {
if (m_vpnLocalAddress == address.ip().toString())
vpnAdapterIndex = netInterface.index();
}
}
#else #else
static const int vpnAdapterIndex = 0; Q_UNUSED(iface)
#endif #endif
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
if (killSwitchEnabled) {
if (vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("vpnServer", m_remoteAddress);
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex); emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
qCritical() << "Failed to enable killswitch";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
}
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
return ErrorCode::InternalError;
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("inetAdapterIndex", inetAdapterIndex);
config.insert("vpnAdapterIndex", vpnAdapterIndex);
config.insert("vpnGateway", m_vpnGateway);
config.insert("vpnServer", m_remoteAddress);
auto enablePeerTraffic = iface->enablePeerTraffic(config);
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
qCritical() << "Failed to enable peer traffic";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
#endif
return ErrorCode::NoError; return ErrorCode::NoError;
}, },
[]() { return ErrorCode::AmneziaServiceConnectionFailed; }); []() { return ErrorCode::AmneziaServiceConnectionFailed; });
+10 -1
View File
@@ -20,14 +20,20 @@ public:
ErrorCode start() override; ErrorCode start() override;
void stop() override; void stop() override;
void setPrimary(const QJsonObject &config) override;
private: private:
enum class Phase {
Inactive,
Active,
Stopping,
};
ErrorCode setupRouting(); ErrorCode setupRouting();
ErrorCode startTun2Socks(); ErrorCode startTun2Socks();
QJsonObject m_xrayConfig; QJsonObject m_xrayConfig;
amnezia::RouteMode m_routeMode; amnezia::RouteMode m_routeMode;
QList<QHostAddress> m_dnsServers;
QString m_remoteAddress; QString m_remoteAddress;
QString m_socksUser; QString m_socksUser;
@@ -38,6 +44,9 @@ private:
int m_tun2socksRetryCount = 0; int m_tun2socksRetryCount = 0;
static constexpr int maxTun2SocksRetries = 5; static constexpr int maxTun2SocksRetries = 5;
static constexpr int tun2socksRetryDelayMs = 400; static constexpr int tun2socksRetryDelayMs = 400;
QString m_tunName;
Phase m_phase = Phase::Inactive;
}; };
#endif // XRAYPROTOCOL_H #endif // XRAYPROTOCOL_H
+157
View File
@@ -0,0 +1,157 @@
#include "tunnel.h"
#include <QTimer>
#include "daemon/interfaceconfig.h"
Tunnel::Tunnel(QString ifname,
amnezia::DockerContainer container,
QJsonObject config,
QString remoteAddress,
QObject* parent)
: QObject(parent),
m_ifname(std::move(ifname)),
m_remoteAddress(std::move(remoteAddress)),
m_container(container),
m_config(std::move(config)) {}
Tunnel::~Tunnel() = default;
void Tunnel::prepare() {
if (m_state != State::Idle) {
return;
}
setState(State::Preparing);
m_config.insert("ifname", m_ifname);
m_protocol.reset(VpnProtocol::factory(m_container, m_config));
if (!m_protocol) {
setState(State::Failed);
emit failed(amnezia::ErrorCode::InternalError);
return;
}
connect(m_protocol.data(), &VpnProtocol::connectionStateChanged,
this, &Tunnel::onProtocolStateChanged);
connect(m_protocol.data(), &VpnProtocol::bytesChanged,
this, &Tunnel::bytesChanged);
connect(m_protocol.data(), &VpnProtocol::tunnelAddressesUpdated,
this, &Tunnel::addressesUpdated);
connect(m_protocol.data(), &VpnProtocol::primaryReady,
this, &Tunnel::onPrimaryReady);
connect(m_protocol.data(), &VpnProtocol::primaryFailed,
this, &Tunnel::onPrimaryFailed);
const amnezia::ErrorCode prepareErr = m_protocol->prepare();
if (prepareErr != amnezia::ErrorCode::NoError) {
setState(State::Failed);
emit failed(prepareErr);
return;
}
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
const amnezia::ErrorCode err = m_protocol->start();
if (err != amnezia::ErrorCode::NoError) {
cancelActivationDeadline();
setState(State::Failed);
emit failed(err);
}
}
void Tunnel::commit() {
if (m_state != State::Prepared) {
return;
}
setState(State::Committing);
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
if (m_protocol) {
m_protocol->setPrimary(m_config);
}
}
void Tunnel::onPrimaryReady() {
if (m_state != State::Committing) {
return;
}
cancelActivationDeadline();
setState(State::Active);
emit activated();
}
void Tunnel::onPrimaryFailed() {
if (m_state != State::Committing) {
return;
}
cancelActivationDeadline();
setState(State::Failed);
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
}
void Tunnel::deactivate() {
if (m_state == State::Gone || m_state == State::Idle) {
return;
}
cancelActivationDeadline();
setState(State::Gone);
if (m_protocol) {
m_protocol->stop();
}
}
void Tunnel::restart() {
deactivate();
setState(State::Idle);
prepare();
}
void Tunnel::setState(State next) {
if (m_state == next) {
return;
}
m_state = next;
emit stateChanged(m_state);
}
void Tunnel::startActivationDeadline(int msec) {
if (!m_deadline) {
m_deadline = new QTimer(this);
m_deadline->setSingleShot(true);
connect(m_deadline, &QTimer::timeout, this, [this]() {
if (m_state != State::Preparing && m_state != State::Committing) {
return;
}
setState(State::Failed);
emit failed(amnezia::ErrorCode::InternalError);
});
}
m_deadline->start(msec);
}
void Tunnel::cancelActivationDeadline() {
if (m_deadline) {
m_deadline->stop();
}
}
void Tunnel::onProtocolStateChanged(Vpn::ConnectionState state) {
if (m_state == State::Preparing && state == Vpn::ConnectionState::Connected) {
cancelActivationDeadline();
setState(State::Prepared);
emit prepared();
return;
}
const bool inLiveState = m_state == State::Preparing
|| m_state == State::Prepared
|| m_state == State::Committing
|| m_state == State::Active;
const bool isFailureSignal = state == Vpn::ConnectionState::Disconnected
|| state == Vpn::ConnectionState::Error;
if (inLiveState && isFailureSignal) {
cancelActivationDeadline();
setState(State::Failed);
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
}
}
+82
View File
@@ -0,0 +1,82 @@
#ifndef TUNNEL_H
#define TUNNEL_H
#include <QJsonObject>
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include "core/protocols/vpnProtocol.h"
#include "core/utils/containerEnum.h"
#include "core/utils/errorCodes.h"
class QTimer;
class Tunnel : public QObject {
Q_OBJECT
public:
enum class State {
Idle,
Preparing,
Prepared,
Committing,
Active,
Gone,
Failed,
};
Q_ENUM(State)
Tunnel(QString ifname,
amnezia::DockerContainer container,
QJsonObject config,
QString remoteAddress,
QObject* parent = nullptr);
~Tunnel() override;
const QString& ifname() const { return m_ifname; }
const QString& remoteAddress() const { return m_remoteAddress; }
amnezia::DockerContainer container() const { return m_container; }
const QJsonObject& config() const { return m_config; }
State state() const { return m_state; }
QSharedPointer<VpnProtocol> protocol() const { return m_protocol; }
const QString& handoverIfname() const { return m_handoverIfname; }
void setHandoverIfname(const QString& ifname) { m_handoverIfname = ifname; }
void clearHandoverIfname() { m_handoverIfname.clear(); }
virtual void prepare();
virtual void commit();
virtual void deactivate();
virtual void restart();
signals:
void prepared();
void activated();
void failed(amnezia::ErrorCode);
void stateChanged(Tunnel::State);
void bytesChanged(quint64 rxBytes, quint64 txBytes);
void addressesUpdated(const QString& gateway, const QString& localAddress);
protected:
void setState(State);
void startActivationDeadline(int msec);
void cancelActivationDeadline();
QString m_ifname;
QString m_remoteAddress;
QString m_handoverIfname;
amnezia::DockerContainer m_container;
QJsonObject m_config;
QSharedPointer<VpnProtocol> m_protocol;
private:
void onProtocolStateChanged(Vpn::ConnectionState state);
void onPrimaryReady();
void onPrimaryFailed();
State m_state = State::Idle;
QTimer* m_deadline = nullptr;
};
#endif // TUNNEL_H
+2
View File
@@ -15,6 +15,8 @@ namespace amnezia
Awg2, Awg2,
WireGuard, WireGuard,
OpenVpn, OpenVpn,
Cloak,
ShadowSocks,
Ipsec, Ipsec,
Xray, Xray,
SSXray, SSXray,
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
{ {
if (c == DockerContainer::None) if (c == DockerContainer::None)
return "none"; return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg) if (c == DockerContainer::Awg)
return "amnezia-awg"; return "amnezia-awg";
if (c == DockerContainer::Awg2) if (c == DockerContainer::Awg2)
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{ {
return { { DockerContainer::None, "Not installed" }, return { { DockerContainer::None, "Not installed" },
{ DockerContainer::OpenVpn, "OpenVPN" }, { DockerContainer::OpenVpn, "OpenVPN" },
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" }, { DockerContainer::Awg2, "AmneziaWG" },
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
return { { DockerContainer::OpenVpn, return { { DockerContainer::OpenVpn,
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") }, "own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::Cloak,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power " QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") }, "consumption.") },
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
ServiceType ContainerUtils::containerService(DockerContainer c) ServiceType ContainerUtils::containerService(DockerContainer c)
{ {
if (isUnsupportedContainer(c)) {
return ServiceType::Vpn;
}
return ProtocolUtils::protocolService(defaultProtocol(c)); return ProtocolUtils::protocolService(defaultProtocol(c));
} }
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
switch (c) { switch (c) {
case DockerContainer::None: return Proto::Unknown; case DockerContainer::None: return Proto::Unknown;
case DockerContainer::OpenVpn: return Proto::OpenVpn; case DockerContainer::OpenVpn: return Proto::OpenVpn;
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks: return Proto::Unknown;
case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg; case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Awg: return Proto::Awg;
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension allow OpenVPN for parity with iOS. // macOS build using Network Extension allow OpenVPN for parity with iOS.
switch (c) { switch (c) {
case DockerContainer::OpenVpn: return true; case DockerContainer::OpenVpn: return true;
case DockerContainer::Cloak: return false;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true; case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
bool ContainerUtils::isShareable(DockerContainer container) bool ContainerUtils::isShareable(DockerContainer container)
{ {
if (isUnsupportedContainer(container)) {
return false;
}
switch (container) { switch (container) {
case DockerContainer::TorWebSite: return false; case DockerContainer::TorWebSite: return false;
case DockerContainer::Dns: return false; case DockerContainer::Dns: return false;
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
return container == DockerContainer::Awg || container == DockerContainer::Awg2; return container == DockerContainer::Awg || container == DockerContainer::Awg2;
} }
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
{
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
}
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{ {
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol)) QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
@@ -45,6 +45,8 @@ namespace amnezia
bool isAwgContainer(DockerContainer container); bool isAwgContainer(DockerContainer container);
bool isUnsupportedContainer(DockerContainer container);
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig); QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
int installPageOrder(DockerContainer container); int installPageOrder(DockerContainer container);
+3 -2
View File
@@ -38,6 +38,8 @@ namespace amnezia
XrayServerConfigInvalid = 215, XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216, XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217, XrayRealityKeysReadFailed = 217,
ServerContainerRuntimeNotSupported = 218,
ContainerRuntimeServiceNotRunning = 219,
// Ssh connection errors // Ssh connection errors
SshRequestDeniedError = 300, SshRequestDeniedError = 300,
@@ -79,6 +81,7 @@ namespace amnezia
ImportBackupFileUseRestoreInstead = 903, ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904, RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905, LegacyApiV1NotSupportedError = 905,
LegacyContainerNotSupportedError = 906,
// Android errors // Android errors
AndroidError = 1000, AndroidError = 1000,
@@ -123,5 +126,3 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode) Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H #endif // ERRORCODES_H
+3
View File
@@ -39,6 +39,8 @@ QString errorString(ErrorCode code) {
case(ErrorCode::XrayRealityKeysReadFailed): case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server"); errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break; break;
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
// Libssh errors // Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -69,6 +71,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break; case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break; case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break; case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break; case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break; case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
+24 -7
View File
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) }; return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100; constexpr int BUFFER_SIZE = 8192;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0; int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0; int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg; struct nlmsghdr *nlh, *nlmsg;
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
// This struct contain route attributes (route type) // This struct contain route attributes (route type)
struct rtattr *route_attribute; struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE]; char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE]; char msgbuf[100], buffer[BUFFER_SIZE];
char *ptr = buffer; char *ptr = buffer;
struct timeval tv; struct timeval tv;
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
nlh = (struct nlmsghdr *) ptr; nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */ /* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) || if((NLMSG_OK(nlh, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR)) (nlh->nlmsg_type == NLMSG_ERROR))
{ {
perror("Error in received packet"); perror("Error in received packet");
return {}; return {};
@@ -355,13 +355,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
} }
/* Break if its not a multi part message */ /* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
break; break;
} }
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
/* parse response */ /* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) int remaining = msg_len + received_bytes;
nlh = (struct nlmsghdr *) buffer;
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
{ {
/* Get the route data */ /* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh); route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
@@ -370,6 +372,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if (route_entry->rtm_table != RT_TABLE_MAIN) if (route_entry->rtm_table != RT_TABLE_MAIN)
continue; continue;
/* Reset per-route to avoid cross-route state pollution */
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
route_attribute = (struct rtattr *) RTM_RTA(route_entry); route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh); route_attribute_len = RTM_PAYLOAD(nlh);
@@ -391,10 +397,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
} }
if ((*gateway_address) && (*interface)) { if ((*gateway_address) && (*interface)) {
if (QString::fromUtf8(interface).startsWith("amn")) {
continue;
}
qDebug() << "Gateway " << gateway_address << " for interface " << interface; qDebug() << "Gateway " << gateway_address << " for interface " << interface;
break; break;
} }
} }
if (!(*gateway_address) || !(*interface))
qDebug() << "getGatewayAndIface: no gateway found";
close(sock); close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) }; return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif #endif
@@ -449,10 +460,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] && sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type]) sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
{ {
char ifname[IF_NAMESIZE] = {0};
const bool isTun = if_indextoname(rt->rtm_index, ifname)
&& QString::fromUtf8(ifname).startsWith("utun");
if (afinet_type[ip_type] == AF_INET) if (afinet_type[ip_type] == AF_INET)
{ {
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{ {
if (isTun) continue;
char dstStr4[INET_ADDRSTRLEN]; char dstStr4[INET_ADDRSTRLEN];
char srcStr4[INET_ADDRSTRLEN]; char srcStr4[INET_ADDRSTRLEN];
memcpy(srcStr4, memcpy(srcStr4,
@@ -470,6 +486,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
{ {
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{ {
if (isTun) continue;
char dstStr6[INET6_ADDRSTRLEN]; char dstStr6[INET6_ADDRSTRLEN];
char srcStr6[INET6_ADDRSTRLEN]; char srcStr6[INET6_ADDRSTRLEN];
memcpy(srcStr6, memcpy(srcStr6,
+633
View File
@@ -0,0 +1,633 @@
#include "vpnTrafficGuard.h"
#include <QDebug>
#include <QEventLoop>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QPointer>
#include <QStringList>
#include <QTimer>
#include <QJsonObject>
#ifdef AMNEZIA_DESKTOP
#include "core/utils/ipcClient.h"
#endif
#include "core/utils/networkUtilities.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/tunnel.h"
#include "mozilla/localsocketcontroller.h"
namespace {
QStringList allowedIpPrefixesFor(const QJsonObject& activateJson)
{
QStringList prefixes;
const QJsonArray ranges = activateJson.value("allowedIPAddressRanges").toArray();
for (const QJsonValue& v : ranges) {
const QJsonObject r = v.toObject();
const QString addr = r.value("address").toString();
if (addr.isEmpty()) continue;
prefixes.append(QStringLiteral("%1/%2").arg(addr).arg(r.value("range").toInt()));
}
return prefixes;
}
QStringList excludedAddressesFor(const QJsonObject& activateJson)
{
QStringList addrs;
const QJsonArray excluded = activateJson.value("excludedAddresses").toArray();
for (const QJsonValue& v : excluded) {
const QString s = v.toString();
if (!s.isEmpty()) addrs.append(s);
}
return addrs;
}
QStringList resolversFor(const QJsonObject& activateJson)
{
QStringList dns;
const QString primary = activateJson.value("primaryDnsServer").toString();
if (!primary.isEmpty()) dns.append(primary);
const QString secondary = activateJson.value("secondaryDnsServer").toString();
if (!secondary.isEmpty()) dns.append(secondary);
return dns;
}
QString interfaceNameForAddress(const QString& address)
{
if (address.isEmpty()) return QString();
const QHostAddress target(address);
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
if (entry.ip() == target) {
return iface.name();
}
}
}
return QString();
}
}
VpnTrafficGuard::VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject *parent)
: QObject(parent), m_appSettingsRepository(appSettings)
{
}
VpnTrafficGuard::~VpnTrafficGuard()
{
}
void VpnTrafficGuard::setConfig(const QJsonObject &config)
{
m_config = config;
}
bool VpnTrafficGuard::allowEndpoint(const QString &remoteAddress, const QString &ifname)
{
#ifdef AMNEZIA_DESKTOP
if (remoteAddress.isEmpty()) {
return false;
}
if (m_allowedEndpoints.contains(remoteAddress)) {
return true;
}
m_allowedEndpoints.append(remoteAddress);
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(ifname, QStringList(remoteAddress));
return reply.waitForFinished(1000) && reply.returnValue();
});
#else
Q_UNUSED(remoteAddress)
Q_UNUSED(ifname)
return true;
#endif
}
void VpnTrafficGuard::setupRoutes(const QJsonObject &vpnConfiguration, const QSharedPointer<VpnProtocol> &protocol, const QString &remoteAddress)
{
#ifdef AMNEZIA_DESKTOP
if (!m_appSettingsRepository) {
qCritical() << "VpnTrafficGuard::setupRoutes: repositories not initialized";
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->resetIpStack();
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnTrafficGuard::setupRoutes: Successfully flushed DNS";
else
qWarning() << "VpnTrafficGuard::setupRoutes: Failed to flush DNS";
const QString proto = vpnConfiguration.value(configKey::vpnProto).toString();
const bool isWgBased = (proto == ProtocolUtils::protoToString(Proto::Awg) ||
proto == ProtocolUtils::protoToString(Proto::WireGuard));
if (!isWgBased) {
if (!protocol) {
qWarning() << "VpnTrafficGuard::setupRoutes: protocol is null";
return;
}
QString dns1 = vpnConfiguration.value(configKey::dns1).toString();
QString dns2 = vpnConfiguration.value(configKey::dns2).toString();
const bool isXrayBased = (proto == ProtocolUtils::protoToString(Proto::Xray) ||
proto == ProtocolUtils::protoToString(Proto::SSXray));
const QString tunIfname = isXrayBased
? vpnConfiguration.value("ifname").toString()
: interfaceNameForAddress(protocol->vpnLocalAddress());
#ifdef Q_OS_LINUX
const QString tunGw = isXrayBased ? protocol->vpnGateway() : QString();
#else
const QString tunGw = protocol->vpnGateway();
#endif
#ifdef Q_OS_MACOS
if (!m_appSettingsRepository->isSitesSplitTunnelingEnabled() || m_appSettingsRepository->routeMode() != amnezia::RouteMode::VpnAllExceptSites) {
iface->routeAddListVia(tunIfname, tunGw, QStringList() << dns1 << dns2);
}
#else
iface->routeAddListVia(tunIfname, tunGw, QStringList() << dns1 << dns2);
#endif
if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) {
iface->routeDeleteList(protocol->vpnGateway(), QStringList() << "0.0.0.0");
if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnOnlyForwardSites) {
QPointer<VpnProtocol> protocolPtr(protocol.data());
QTimer::singleShot(1000, protocol.data(),
[this, protocolPtr]() {
if (!protocolPtr) {
return;
}
addSplitTunnelRoutes(protocolPtr->vpnGateway(), m_appSettingsRepository->routeMode());
});
} else if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnAllExceptSites) {
iface->routeAddListVia(tunIfname, tunGw, QStringList() << "0.0.0.0/1" << "128.0.0.0/1");
iface->routeAddList(protocol->routeGateway(), QStringList() << remoteAddress);
#ifdef Q_OS_MACOS
iface->routeAddList(protocol->routeGateway(), QStringList() << dns1 << dns2);
#endif
addSplitTunnelRoutes(protocol->routeGateway(), m_appSettingsRepository->routeMode());
}
}
}
});
#endif
}
void VpnTrafficGuard::addSplitTunnelRoutes(const QString &gw, amnezia::RouteMode mode)
{
#ifdef AMNEZIA_DESKTOP
if (!m_appSettingsRepository) {
qCritical() << "VpnTrafficGuard::addSplitTunnelRoutes: repositories not initialized";
return;
}
QStringList ips;
QStringList sites;
const QVariantMap &m = m_appSettingsRepository->vpnSites(mode);
for (auto i = m.constBegin(); i != m.constEnd(); ++i) {
if (NetworkUtilities::checkIpSubnetFormat(i.key())) {
ips.append(i.key());
} else {
if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) {
ips.append(i.value().toString());
}
sites.append(i.key());
}
}
ips.removeDuplicates();
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->routeAddList(gw, ips);
});
for (const QString &site : sites) {
const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) {
for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
const QString &ip = addr.toString();
if (!ips.contains(ip)) {
IpcClient::withInterface([gw, ip](QSharedPointer<IpcInterfaceReplica> iface) {
iface->routeAddList(gw, QStringList() << ip);
});
m_appSettingsRepository->addVpnSite(mode, site, ip);
}
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (!reply.waitForFinished() || !reply.returnValue())
qWarning() << "VpnTrafficGuard::addSplitTunnelRoutes: Failed to flush DNS";
});
break;
}
}
};
QHostInfo::lookupHost(site, this, cbResolv);
}
#endif
}
void VpnTrafficGuard::finishFirewallHandover(Tunnel* tunnel)
{
#if defined(AMNEZIA_DESKTOP) && defined(Q_OS_WIN)
if (!tunnel) return;
const QString handoverIfname = tunnel->handoverIfname();
if (handoverIfname.isEmpty() || handoverIfname == tunnel->ifname()) {
tunnel->clearHandoverIfname();
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->disableKillSwitchForTunnel(handoverIfname, QStringList());
});
tunnel->clearHandoverIfname();
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::applyKillSwitch(Tunnel* tunnel, const QString &gateway, const QString &localAddress)
{
#ifdef AMNEZIA_DESKTOP
finishFirewallHandover(tunnel);
QJsonObject updatedConfig = m_config;
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
#ifdef Q_OS_WIN
const bool engineNamedInterface = VpnProtocol::isWireGuardBased(tunnel->container())
|| VpnProtocol::isXrayBased(tunnel->container());
const QString ifname = engineNamedInterface ? updatedConfig.value("ifname").toString() : QString();
if (!ifname.isEmpty()) {
updatedConfig.insert("vpnGateway", gateway);
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
iface->enableKillSwitch(updatedConfig, 0);
}
iface->enablePeerTraffic(updatedConfig);
} else {
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
if (localAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
updatedConfig.insert("vpnAdapterIndex", netInterfaces.at(i).index());
updatedConfig.insert("vpnGateway", gateway);
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
iface->enableKillSwitch(updatedConfig, netInterfaces.at(i).index());
}
iface->enablePeerTraffic(updatedConfig);
}
}
}
}
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
updatedConfig.insert("vpnServer",
NetworkUtilities::getIPAddress(updatedConfig.value(amnezia::configKey::hostName).toString()));
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(updatedConfig, 0);
//TODO: why it takes so long?
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::applyKillSwitch: Failed to enable killswitch";
} else {
qDebug() << "VpnTrafficGuard::applyKillSwitch: Successfully enabled killswitch";
}
}
#endif
const QString proto = updatedConfig.value(configKey::vpnProto).toString();
const bool isXrayBased = (proto == ProtocolUtils::protoToString(Proto::Xray) ||
proto == ProtocolUtils::protoToString(Proto::SSXray));
if (isXrayBased) {
if (updatedConfig.value(configKey::splitTunnelType).toInt() == amnezia::route_mode_ns::VpnAllSites) {
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
const QString xrayIfname = tunnel->ifname();
auto routeAddList = iface->routeAddListVia(xrayIfname, gateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
} else {
m_ipv6RoutingStopped = true;
}
}
});
#endif
}
void VpnTrafficGuard::flushAll()
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->restoreTunnelResolvers();
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
m_allowedEndpoints.clear();
//TODO: why it takes so long?
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::flushAll: Failed to disable killswitch";
} else {
qDebug() << "VpnTrafficGuard::flushAll: Successfully disabled killswitch";
}
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnTrafficGuard::flushAll: Successfully flushed DNS";
else
qWarning() << "VpnTrafficGuard::flushAll: Failed to flush DNS";
auto clearSavedRoutes = iface->clearSavedRoutes();
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
qDebug() << "VpnTrafficGuard::flushAll: Successfully cleared saved routes";
else
qWarning() << "VpnTrafficGuard::flushAll: Failed to clear saved routes";
if (m_ipv6RoutingStopped) {
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) {
qCritical() << "Failed to enable IPv6 routing";
} else {
m_ipv6RoutingStopped = false;
}
}
});
#endif
}
void VpnTrafficGuard::reserve(Tunnel* tunnel)
{
if (!tunnel) return;
#ifdef AMNEZIA_DESKTOP
const bool engineNamedInterface = VpnProtocol::isWireGuardBased(tunnel->container())
|| VpnProtocol::isXrayBased(tunnel->container());
allowEndpoint(tunnel->remoteAddress(), engineNamedInterface ? tunnel->ifname() : QString());
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::release(Tunnel* tunnel)
{
if (!tunnel) return;
disconnect(tunnel, nullptr, this, nullptr);
#ifdef AMNEZIA_DESKTOP
const bool engineNamedInterface = VpnProtocol::isWireGuardBased(tunnel->container())
|| VpnProtocol::isXrayBased(tunnel->container());
m_allowedEndpoints.removeAll(tunnel->remoteAddress());
IpcClient::withInterface([this, &tunnel, engineNamedInterface](QSharedPointer<IpcInterfaceReplica> iface) {
iface->disableKillSwitchForTunnel(engineNamedInterface ? tunnel->ifname() : QString(), m_allowedEndpoints);
});
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::applyPolicy(Tunnel* tunnel)
{
if (!tunnel) return;
#ifdef AMNEZIA_DESKTOP
const QString ifname = tunnel->ifname();
if (VpnProtocol::isXrayBased(tunnel->container())) {
const QJsonObject cfg = tunnel->config();
const QString primary = cfg.value(amnezia::configKey::dns1).toString();
const QString secondary = cfg.value(amnezia::configKey::dns2).toString();
QList<QHostAddress> dns;
if (!primary.isEmpty()) dns.append(QHostAddress(primary));
if (!secondary.isEmpty() && secondary != primary) dns.append(QHostAddress(secondary));
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto updateRes = iface->updateResolvers(ifname, dns);
if (!updateRes.waitForFinished() || !updateRes.returnValue()) {
qWarning() << "VpnTrafficGuard::applyPolicy: updateResolvers failed for" << ifname;
}
#ifdef Q_OS_MAC
const auto gw = NetworkUtilities::getGatewayAndIface();
const QString uplinkIface = gw.second.name();
const QString uplinkGateway = gw.first;
if (!uplinkIface.isEmpty() && !uplinkGateway.isEmpty()) {
auto add = iface->xrayAddUplinkRoutes(uplinkIface, uplinkGateway);
if (!add.waitForFinished() || !add.returnValue()) {
qWarning() << "VpnTrafficGuard::applyPolicy: xrayAddUplinkRoutes failed on" << uplinkIface;
}
}
#endif
});
return;
}
if (!VpnProtocol::isWireGuardBased(tunnel->container())) {
#ifdef Q_OS_LINUX
const auto protocol = tunnel->protocol();
const QString realIfname = protocol ? interfaceNameForAddress(protocol->vpnLocalAddress()) : QString();
if (realIfname.isEmpty()) {
qWarning() << "VpnTrafficGuard::applyPolicy: could not resolve interface for tunnel DNS";
return;
}
const QJsonObject cfg = tunnel->config();
const QString primary = cfg.value(amnezia::configKey::dns1).toString();
const QString secondary = cfg.value(amnezia::configKey::dns2).toString();
QList<QHostAddress> dnsResolvers;
if (!primary.isEmpty()) dnsResolvers.append(QHostAddress(primary));
if (!secondary.isEmpty() && secondary != primary) dnsResolvers.append(QHostAddress(secondary));
if (!dnsResolvers.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto updateRes = iface->updateResolvers(realIfname, dnsResolvers);
if (!updateRes.waitForFinished() || !updateRes.returnValue()) {
qWarning() << "VpnTrafficGuard::applyPolicy: updateResolvers failed for" << realIfname;
}
});
}
#endif
return;
}
const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname());
const QStringList prefixes = allowedIpPrefixesFor(activate);
const QStringList excluded = excludedAddressesFor(activate);
const QStringList dns = resolversFor(activate);
const QString peer = tunnel->remoteAddress();
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (!peer.isEmpty()) iface->addExclusionRoute(ifname, peer);
for (const QString& addr : excluded) {
iface->addExclusionRoute(ifname, addr);
}
for (const QString& prefix : prefixes) {
iface->addAllowedIp(ifname, prefix);
}
iface->setTunnelResolvers(ifname, dns);
iface->flushDns();
});
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::revokePolicy(Tunnel* tunnel)
{
if (!tunnel) return;
#ifdef AMNEZIA_DESKTOP
const QString ifname = tunnel->ifname();
if (VpnProtocol::isXrayBased(tunnel->container())) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->restoreResolvers();
#ifdef Q_OS_MAC
const auto gw = NetworkUtilities::getGatewayAndIface();
const QString uplinkIface = gw.second.name();
const QString uplinkGateway = gw.first;
if (!uplinkIface.isEmpty()) {
iface->xrayRemoveUplinkRoutes(uplinkIface, uplinkGateway);
}
#endif
});
return;
}
if (!VpnProtocol::isWireGuardBased(tunnel->container())) {
#ifdef Q_OS_LINUX
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->restoreResolvers();
});
#endif
return;
}
const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname());
const QStringList prefixes = allowedIpPrefixesFor(activate);
const QStringList excluded = excludedAddressesFor(activate);
const QString peer = tunnel->remoteAddress();
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
for (const QString& prefix : prefixes) {
iface->delAllowedIp(ifname, prefix);
}
for (const QString& addr : excluded) {
iface->delExclusionRoute(ifname, addr);
}
if (!peer.isEmpty()) iface->delExclusionRoute(ifname, peer);
});
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::bringUp(Tunnel* tunnel)
{
if (!tunnel) return;
reserve(tunnel);
tunnel->prepare();
}
void VpnTrafficGuard::commit(Tunnel* tunnel)
{
if (!tunnel) return;
#ifdef Q_OS_WIN
const QString ipv4 = tunnel->config().value(QStringLiteral("deviceIpv4Address")).toString();
const QString ipv6 = tunnel->config().value(QStringLiteral("deviceIpv6Address")).toString();
if (!ipv4.isEmpty() || !ipv6.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto ap = iface->applyAdapterAddress(tunnel->ifname(), ipv4, ipv6);
if (!ap.waitForFinished(15000) || !ap.returnValue()) {
qWarning() << "VpnTrafficGuard::commit: applyAdapterAddress failed for"
<< tunnel->ifname();
}
});
}
#endif
applyPolicy(tunnel);
connect(tunnel, &Tunnel::activated, this, [this, tunnel] {
if (auto p = tunnel->protocol()) {
applyKillSwitch(tunnel, p->vpnGateway(), p->vpnLocalAddress());
}
});
#ifdef Q_OS_WIN
connect(tunnel, &Tunnel::addressesUpdated, this,
[this, tunnel](const QString& gw, const QString& la) {
applyKillSwitch(tunnel, gw, la);
});
#endif
tunnel->commit();
}
void VpnTrafficGuard::tearDown(Tunnel* tunnel)
{
if (!tunnel) return;
revokePolicy(tunnel);
release(tunnel);
tunnel->deactivate();
}
void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to)
{
if (!to) return;
if (from) {
to->setHandoverIfname(from->ifname());
}
#ifdef Q_OS_WIN
if (from) {
const QString fromIpv4 = from->config().value(QStringLiteral("deviceIpv4Address")).toString();
const QString fromIpv6 = from->config().value(QStringLiteral("deviceIpv6Address")).toString();
if (!fromIpv4.isEmpty() || !fromIpv6.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto rm = iface->removeAdapterAddress(from->ifname(), fromIpv4, fromIpv6);
if (!rm.waitForFinished(2000) || !rm.returnValue()) {
qWarning() << "VpnTrafficGuard::swap: removeAdapterAddress failed for"
<< from->ifname();
}
});
}
}
#endif
QEventLoop loop;
QTimer guard;
guard.setSingleShot(true);
const auto activatedConn = connect(to, &Tunnel::activated, &loop, &QEventLoop::quit);
const auto failedConn = connect(to, &Tunnel::failed, &loop, [&loop](amnezia::ErrorCode) { loop.quit(); });
const auto timeoutConn = connect(&guard, &QTimer::timeout, &loop, [&loop]() {
qWarning() << "VpnTrafficGuard::swap: timed out waiting for new tunnel activation";
loop.quit();
});
commit(to);
guard.start(5000);
loop.exec();
guard.stop();
disconnect(activatedConn);
disconnect(failedConn);
disconnect(timeoutConn);
// Service IPCs are processed in order on a single connection. Wait on a trailing
// sync request so the killswitch enable from the activation handlers is fully
// applied before we deactivate the previous tunnel.
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (!reply.waitForFinished(5000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::swap: trailing sync IPC timed out or failed";
}
});
if (from) {
m_allowedEndpoints.removeAll(from->remoteAddress());
#ifndef Q_OS_WIN
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
iface->resetKillSwitchAllowedRange(m_allowedEndpoints);
});
#endif
revokePolicy(from);
from->deactivate();
#ifdef Q_OS_MAC
if (VpnProtocol::isXrayBased(to->container())) {
if (auto p = to->protocol()) {
applyKillSwitch(to, p->vpnGateway(), p->vpnLocalAddress());
setupRoutes(to->config(), p, to->remoteAddress());
}
}
#endif
}
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef VPNTRAFFICGUARD_H
#define VPNTRAFFICGUARD_H
#include <qobject.h>
#include "core/repositories/secureAppSettingsRepository.h"
#include "protocols/vpnProtocol.h"
class Tunnel;
class VpnTrafficGuard : public QObject
{
Q_OBJECT
public:
explicit VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject* parent = nullptr);
~VpnTrafficGuard() override;
void setConfig(const QJsonObject &config);
void setupRoutes(const QJsonObject &vpnConfiguration,
const QSharedPointer<VpnProtocol> &protocol,
const QString &remoteAddress);
void flushAll();
bool allowEndpoint(const QString &remoteAddress, const QString &ifname = QString());
void applyKillSwitch(Tunnel* tunnel, const QString &vpnGateway, const QString &vpnLocalAddress);
void reserve(Tunnel* tunnel);
void release(Tunnel* tunnel);
void applyPolicy(Tunnel* tunnel);
void revokePolicy(Tunnel* tunnel);
void bringUp(Tunnel* tunnel);
void commit(Tunnel* tunnel);
void tearDown(Tunnel* tunnel);
void swap(Tunnel* from, Tunnel* to);
private:
void addSplitTunnelRoutes(const QString &gateway, amnezia::RouteMode mode);
void finishFirewallHandover(Tunnel* tunnel);
SecureAppSettingsRepository* m_appSettingsRepository;
QJsonObject m_config;
bool m_ipv6RoutingStopped = false;
QStringList m_allowedEndpoints;
};
#endif // VPNTRAFFICGUARD_H
+173 -211
View File
@@ -34,8 +34,8 @@ Daemon::Daemon(QObject* parent) : QObject(parent) {
Q_ASSERT(s_daemon == nullptr); Q_ASSERT(s_daemon == nullptr);
s_daemon = this; s_daemon = this;
m_handshakeTimer.setSingleShot(true); m_activationTimer.setSingleShot(false);
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake); connect(&m_activationTimer, &QTimer::timeout, this, &Daemon::checkActivations);
} }
Daemon::~Daemon() { Daemon::~Daemon() {
@@ -43,6 +43,9 @@ Daemon::~Daemon() {
logger.debug() << "Daemon released"; logger.debug() << "Daemon released";
qDeleteAll(m_tunnels);
m_tunnels.clear();
Q_ASSERT(s_daemon == this); Q_ASSERT(s_daemon == this);
s_daemon = nullptr; s_daemon = nullptr;
} }
@@ -53,69 +56,38 @@ Daemon* Daemon::instance() {
return s_daemon; return s_daemon;
} }
bool Daemon::activate(const InterfaceConfig& config) { bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
Q_ASSERT(wgutils() != nullptr); logger.debug() << "Activating tunnel";
// There are 3 possible scenarios in which this method is called: WireguardUtils* wg = m_tunnels.value(ifname);
// if (!wg) {
// 1. the VPN is off: the method tries to enable the VPN. wg = createWgUtils();
// 2. the VPN is on and the platform doesn't support the server-switching: if (!wg) {
// this method calls deactivate() and then it continues as 1. logger.error() << "Failed to create wireguard utils.";
// 3. the VPN is on and the platform supports the server-switching: this
// method calls switchServer().
//
// At the end, if the activation succeds, the `connected` signal is emitted.
// If the activation abort's for any reason `the `activationFailure` signal is
// emitted.
logger.debug() << "Activating interface";
auto emit_failure_guard = qScopeGuard([this] { emit activationFailure(); });
if (m_connections.contains(config.m_hopType)) {
if (supportServerSwitching(config)) {
logger.debug() << "Already connected. Server switching supported.";
if (!switchServer(config)) {
return false; return false;
} }
m_tunnels.insert(ifname, wg);
if (!dnsutils()->restoreResolvers()) { }
return false; if (m_primaryIfname.isEmpty()) {
m_primaryIfname = ifname;
} }
if (!maybeUpdateResolvers(config)) { ConnectionState& cs = m_connections[ifname];
return false; cs.m_config = config;
} cs.m_date = QDateTime();
cs.m_deadline = QDateTime::currentDateTime().addMSecs(ACTIVATION_TIMEOUT_MSEC);
bool status = run(Switch, config); auto failure_guard = qScopeGuard([this, ifname] {
logger.debug() << "Connection status:" << status; deactivateTunnel(ifname);
if (status) { });
m_connections[config.m_hopType] = ConnectionState(config);
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
emit_failure_guard.dismiss();
return true;
}
return false;
}
logger.warning() << "Already connected. Server switching not supported.";
if (!deactivate(false)) {
return false;
}
Q_ASSERT(!m_connections.contains(config.m_hopType));
if (activate(config)) {
emit_failure_guard.dismiss();
return true;
}
return false;
}
prepareActivation(config); prepareActivation(config);
// Bring up the wireguard interface if not already done. // Bring up the wireguard interface if not already done.
if (!wgutils()->interfaceExists()) { if (!wg->interfaceExists()) {
// Create the interface. InterfaceConfig bringupConfig = config;
if (!wgutils()->addInterface(config)) { bringupConfig.m_deferAddressSetup = (m_primaryIfname != ifname);
if (!wg->addInterface(bringupConfig)) {
logger.error() << "Interface creation failed."; logger.error() << "Interface creation failed.";
return false; return false;
} }
@@ -131,60 +103,71 @@ bool Daemon::activate(const InterfaceConfig& config) {
} }
} }
// Configure routing for excluded addresses.
for (const QString& i : config.m_excludedAddresses) {
addExclusionRoute(IPAddress(i));
}
// Add the peer to this interface. // Add the peer to this interface.
if (!wgutils()->updatePeer(config)) { if (!wg->updatePeer(config)) {
logger.error() << "Peer creation failed."; logger.error() << "Peer creation failed.";
return false; return false;
} }
if (!maybeUpdateResolvers(config)) { if (!m_activationTimer.isActive()) {
return false; m_activationTimer.start(HANDSHAKE_POLL_MSEC);
} }
// set routing failure_guard.dismiss();
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
logger.debug() << "Routing configuration failed for" << ip.toString();
return false;
}
}
bool status = run(Up, config);
logger.debug() << "Connection status:" << status;
if (status) {
m_connections[config.m_hopType] = ConnectionState(config);
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
emit_failure_guard.dismiss();
return true; return true;
}
return false;
} }
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { bool Daemon::setPrimary(const QString& ifname, const InterfaceConfig& config) {
if ((config.m_hopType == InterfaceConfig::MultiHopExit) || WireguardUtils* wg = m_tunnels.value(ifname);
(config.m_hopType == InterfaceConfig::SingleHop)) { if (!wg) {
QList<QHostAddress> resolvers; logger.error() << "setPrimary: no tunnel for" << ifname;
resolvers.append(QHostAddress(config.m_primaryDnsServer));
if (!config.m_secondaryDnsServer.isEmpty()) {
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
}
// If the DNS is not the Gateway, it's a user defined DNS
// thus, not add any other :)
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
}
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
return false; return false;
} }
logger.debug() << "setPrimary" << wg->interfaceName();
const QString priorPrimary = m_primaryIfname;
m_primaryIfname = ifname;
auto failure_guard = qScopeGuard([this, ifname, priorPrimary] {
deactivateTunnel(ifname);
m_primaryIfname = priorPrimary;
});
if (!run(Up, config)) {
return false;
} }
m_connections[ifname].m_config = config;
failure_guard.dismiss();
return true;
}
bool Daemon::deactivateTunnel(const QString& ifname) {
WireguardUtils* wg = m_tunnels.value(ifname);
const ConnectionState cs = m_connections.value(ifname);
const InterfaceConfig& config = cs.m_config;
const bool wasPrimary = (ifname == m_primaryIfname);
const bool isLastTunnel = wg && m_tunnels.size() == 1;
if (wg) {
logger.debug() << "deactivateTunnel" << wg->interfaceName();
if (isLastTunnel) {
for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
wg->deleteExclusionRoute(prefix);
}
m_excludedAddrSet.clear();
}
wg->deletePeer(config);
wg->deleteInterface();
m_tunnels.remove(ifname);
delete wg;
}
m_connections.remove(ifname);
if (wasPrimary) {
m_primaryIfname.clear();
}
return true; return true;
} }
@@ -209,26 +192,60 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
return true; return true;
} }
bool Daemon::addExclusionRoute(const IPAddress& prefix) { bool Daemon::addExclusionRoute(const QString &ifname, const QString &addr) {
IPAddress prefix(addr);
if (m_excludedAddrSet.contains(prefix)) { if (m_excludedAddrSet.contains(prefix)) {
m_excludedAddrSet[prefix]++; m_excludedAddrSet[prefix]++;
return true; return true;
} }
if (!wgutils()->addExclusionRoute(prefix)) { WireguardUtils* wg = wgutilsFor(ifname);
if (!wg) wg = primaryWgutils();
if (!wg || !wg->addExclusionRoute(prefix)) {
return false; return false;
} }
m_excludedAddrSet[prefix] = 1; m_excludedAddrSet[prefix] = 1;
return true; return true;
} }
bool Daemon::delExclusionRoute(const IPAddress& prefix) { bool Daemon::delExclusionRoute(const QString &ifname, const QString &addr) {
Q_ASSERT(m_excludedAddrSet.contains(prefix)); IPAddress prefix(addr);
if (!m_excludedAddrSet.contains(prefix)) {
return false;
}
if (m_excludedAddrSet[prefix] > 1) { if (m_excludedAddrSet[prefix] > 1) {
m_excludedAddrSet[prefix]--; m_excludedAddrSet[prefix]--;
return true; return true;
} }
m_excludedAddrSet.remove(prefix); m_excludedAddrSet.remove(prefix);
return wgutils()->deleteExclusionRoute(prefix); WireguardUtils* wg = wgutilsFor(ifname);
if (!wg) wg = primaryWgutils();
return wg && wg->deleteExclusionRoute(prefix);
}
bool Daemon::addAllowedIp(const QString &ifname, const QString &prefix) {
WireguardUtils* wg = wgutilsFor(ifname);
return wg && wg->updateRoutePrefix(IPAddress(prefix));
}
bool Daemon::delAllowedIp(const QString &ifname, const QString &prefix) {
WireguardUtils* wg = wgutilsFor(ifname);
return wg && wg->deleteRoutePrefix(IPAddress(prefix));
}
bool Daemon::setTunnelResolvers(const QString &ifname, const QStringList &resolvers) {
WireguardUtils* wg = wgutilsFor(ifname);
if (!wg || !dnsutils()) {
return false;
}
QList<QHostAddress> hostAddrs;
for (const QString& r : resolvers) {
hostAddrs.append(QHostAddress(r));
}
return dnsutils()->updateResolvers(wg->interfaceName(), hostAddrs);
}
bool Daemon::restoreTunnelResolvers() {
return dnsutils() && dnsutils()->restoreResolvers();
} }
// static // static
@@ -440,17 +457,16 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("I5").isNull()) { if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString(); config.m_specialJunk["I5"] = obj.value("I5").toString();
} }
config.m_ifname = obj.value("ifname").toString();
return true; return true;
} }
bool Daemon::deactivate(bool emitSignals) { bool Daemon::deactivate(bool emitSignals) {
Q_ASSERT(wgutils() != nullptr); const QString primary = m_primaryIfname;
// Deactivate the main interface. if (m_connections.contains(primary)) {
if (!m_connections.isEmpty()) { if (!run(Down, m_connections.value(primary).m_config)) {
const ConnectionState& state = m_connections.first();
if (!run(Down, state.m_config)) {
return false; return false;
} }
} }
@@ -459,31 +475,26 @@ bool Daemon::deactivate(bool emitSignals) {
emit disconnected(); emit disconnected();
} }
// Cleanup DNS const QStringList ifnames = m_tunnels.keys();
if (!dnsutils()->restoreResolvers()) { for (const QString& ifname : ifnames) {
logger.warning() << "Failed to restore DNS resolvers."; if (ifname != primary) {
deactivateTunnel(ifname);
}
} }
// Cleanup peers and routing if (auto* wg = primaryWgutils()) {
for (const ConnectionState& state : m_connections) { for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
const InterfaceConfig& config = state.m_config; wg->deleteExclusionRoute(prefix);
logger.debug() << "Deleting routes for" << config.m_hopType;
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
wgutils()->deleteRoutePrefix(ip);
} }
wgutils()->deletePeer(config);
}
// Cleanup routing for excluded addresses.
for (auto iterator = m_excludedAddrSet.constBegin();
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
wgutils()->deleteExclusionRoute(iterator.key());
} }
m_excludedAddrSet.clear(); m_excludedAddrSet.clear();
m_connections.clear(); if (m_tunnels.contains(primary)) {
// Delete the interface deactivateTunnel(primary);
return wgutils()->deleteInterface(); }
m_activationTimer.stop();
return true;
} }
QString Daemon::logs() { QString Daemon::logs() {
@@ -492,79 +503,18 @@ QString Daemon::logs() {
void Daemon::cleanLogs() { } void Daemon::cleanLogs() { }
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
if (!m_connections.contains(config.m_hopType)) {
return false;
}
const InterfaceConfig& current =
m_connections.value(config.m_hopType).m_config;
return current.m_privateKey == config.m_privateKey &&
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
current.m_deviceIpv6Address == config.m_deviceIpv6Address &&
current.m_serverIpv4Gateway == config.m_serverIpv4Gateway &&
current.m_serverIpv6Gateway == config.m_serverIpv6Gateway;
}
bool Daemon::switchServer(const InterfaceConfig& config) {
Q_ASSERT(wgutils() != nullptr);
logger.debug() << "Switching server for" << config.m_hopType;
Q_ASSERT(m_connections.contains(config.m_hopType));
const InterfaceConfig& lastConfig =
m_connections.value(config.m_hopType).m_config;
// Configure routing for new excluded addresses.
for (const QString& i : config.m_excludedAddresses) {
addExclusionRoute(IPAddress(i));
}
// Activate the new peer and its routes.
if (!wgutils()->updatePeer(config)) {
logger.error() << "Server switch failed to update the wireguard interface";
return false;
}
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
logger.error() << "Server switch failed to update the routing table";
break;
}
}
// Remove routing entries for the old peer.
for (const QString& i : lastConfig.m_excludedAddresses) {
delExclusionRoute(QHostAddress(i));
}
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
if (!config.m_allowedIPAddressRanges.contains(ip)) {
wgutils()->deleteRoutePrefix(ip);
}
}
// Remove the old peer if it is no longer necessary.
if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) {
if (!wgutils()->deletePeer(lastConfig)) {
return false;
}
}
m_connections[config.m_hopType] = ConnectionState(config);
return true;
}
QJsonObject Daemon::getStatus() { QJsonObject Daemon::getStatus() {
Q_ASSERT(wgutils() != nullptr);
QJsonObject json; QJsonObject json;
logger.debug() << "Status request"; logger.debug() << "Status request";
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) { WireguardUtils* wg = primaryWgutils();
if (!wg || !wg->interfaceExists() || !m_connections.contains(m_primaryIfname)) {
json.insert("connected", QJsonValue(false)); json.insert("connected", QJsonValue(false));
return json; return json;
} }
const ConnectionState& connection = m_connections.first(); const ConnectionState& connection = m_connections.value(m_primaryIfname);
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus(); QList<WireguardUtils::PeerStatus> peers = wg->getPeerStatus();
for (const WireguardUtils::PeerStatus& status : peers) { for (const WireguardUtils::PeerStatus& status : peers) {
if (status.m_pubkey != connection.m_config.m_serverPublicKey) { if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
continue; continue;
@@ -584,38 +534,50 @@ QJsonObject Daemon::getStatus() {
return json; return json;
} }
void Daemon::checkHandshake() { void Daemon::checkActivations() {
Q_ASSERT(wgutils() != nullptr); const QDateTime now = QDateTime::currentDateTime();
QStringList timedOut;
bool anyPending = false;
logger.debug() << "Checking for handshake..."; for (auto it = m_connections.begin(); it != m_connections.end(); ++it) {
const QString& ifname = it.key();
int pendingHandshakes = 0; ConnectionState& cs = it.value();
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus(); if (cs.m_date.isValid()) {
for (ConnectionState& connection : m_connections) { continue; // already handshaked
const InterfaceConfig& config = connection.m_config;
if (connection.m_date.isValid()) {
continue;
} }
logger.debug() << "awaiting" << config.m_serverPublicKey; logger.debug() << "awaiting" << cs.m_config.m_serverPublicKey;
// Check if the handshake has completed. WireguardUtils* wg = m_tunnels.value(ifname);
for (const WireguardUtils::PeerStatus& status : peers) { bool handshaked = false;
if (config.m_serverPublicKey != status.m_pubkey) { if (wg) {
for (const WireguardUtils::PeerStatus& status : wg->getPeerStatus()) {
if (status.m_pubkey != cs.m_config.m_serverPublicKey) {
continue; continue;
} }
if (status.m_handshake != 0) { if (status.m_handshake != 0) {
connection.m_date.setMSecsSinceEpoch(status.m_handshake); cs.m_date.setMSecsSinceEpoch(status.m_handshake);
emit connected(status.m_pubkey); emit tunnelConnected(ifname, status.m_pubkey);
handshaked = true;
}
}
}
if (handshaked) {
continue;
}
if (cs.m_deadline.isValid() && now > cs.m_deadline) {
timedOut.append(ifname);
} else {
anyPending = true;
} }
} }
if (!connection.m_date.isValid()) { for (const QString& ifname : timedOut) {
pendingHandshakes++; logger.warning() << "Tunnel handshake timed out:" << m_tunnels.value(ifname)->interfaceName();
} emit tunnelHandshakeFailed(ifname);
deactivateTunnel(ifname);
} }
// Check again if there were connections that haven't completed a handshake. if (!anyPending) {
if (pendingHandshakes > 0) { m_activationTimer.stop();
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
} }
} }
+25 -18
View File
@@ -22,7 +22,6 @@ class Daemon : public QObject {
enum Op { enum Op {
Up, Up,
Down, Down,
Switch,
}; };
explicit Daemon(QObject* parent); explicit Daemon(QObject* parent);
@@ -32,10 +31,22 @@ class Daemon : public QObject {
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config); static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
virtual bool activate(const InterfaceConfig& config); bool activate(const QString& ifname, const InterfaceConfig& config);
bool setPrimary(const QString& ifname, const InterfaceConfig& config);
bool deactivateTunnel(const QString& ifname);
virtual bool deactivate(bool emitSignals = true); virtual bool deactivate(bool emitSignals = true);
virtual QJsonObject getStatus(); virtual QJsonObject getStatus();
bool addExclusionRoute(const QString &ifname, const QString &addr);
bool delExclusionRoute(const QString &ifname, const QString &addr);
bool addAllowedIp(const QString &ifname, const QString &prefix);
bool delAllowedIp(const QString &ifname, const QString &prefix);
bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers);
bool restoreTunnelResolvers();
const QString& primaryIfname() const { return m_primaryIfname; }
WireguardUtils* wgutilsFor(const QString& ifname) const { return m_tunnels.value(ifname); }
// Callback before any Activating measure is done // Callback before any Activating measure is done
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) { virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
Q_UNUSED(config) }; Q_UNUSED(config) };
@@ -46,19 +57,15 @@ class Daemon : public QObject {
void cleanLogs(); void cleanLogs();
signals: signals:
void connected(const QString& pubkey); void tunnelConnected(const QString& ifname, const QString& pubkey);
/** void tunnelHandshakeFailed(const QString& ifname);
* Can be fired if a call to activate() was unsucessfull
* and connected systems should rollback
*/
void activationFailure();
void disconnected(); void disconnected();
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL); void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
private: private:
bool maybeUpdateResolvers(const InterfaceConfig& config); void checkActivations();
bool addExclusionRoute(const IPAddress& address); WireguardUtils* primaryWgutils() const { return m_tunnels.value(m_primaryIfname); }
bool delExclusionRoute(const IPAddress& address); QTimer m_activationTimer;
protected: protected:
virtual bool run(Op op, const InterfaceConfig& config) { virtual bool run(Op op, const InterfaceConfig& config) {
@@ -66,9 +73,11 @@ class Daemon : public QObject {
Q_UNUSED(config); Q_UNUSED(config);
return true; return true;
} }
virtual bool supportServerSwitching(const InterfaceConfig& config) const; virtual WireguardUtils* createWgUtils() = 0;
virtual bool switchServer(const InterfaceConfig& config);
virtual WireguardUtils* wgutils() const = 0; QMap<QString, WireguardUtils*> m_tunnels;
QString m_primaryIfname;
virtual bool supportIPUtils() const { return false; } virtual bool supportIPUtils() const { return false; }
virtual IPUtils* iputils() { return nullptr; } virtual IPUtils* iputils() { return nullptr; }
virtual DnsUtils* dnsutils() { return nullptr; } virtual DnsUtils* dnsutils() { return nullptr; }
@@ -76,18 +85,16 @@ class Daemon : public QObject {
static bool parseStringList(const QJsonObject& obj, const QString& name, static bool parseStringList(const QJsonObject& obj, const QString& name,
QStringList& list); QStringList& list);
void checkHandshake();
class ConnectionState { class ConnectionState {
public: public:
ConnectionState(){}; ConnectionState(){};
ConnectionState(const InterfaceConfig& config) { m_config = config; } ConnectionState(const InterfaceConfig& config) { m_config = config; }
QDateTime m_date; QDateTime m_date;
QDateTime m_deadline;
InterfaceConfig m_config; InterfaceConfig m_config;
}; };
QMap<InterfaceConfig::HopType, ConnectionState> m_connections; QMap<QString, ConnectionState> m_connections;
QHash<IPAddress, int> m_excludedAddrSet; QHash<IPAddress, int> m_excludedAddrSet;
QTimer m_handshakeTimer;
}; };
#endif // DAEMON_H #endif // DAEMON_H
+44 -8
View File
@@ -31,8 +31,10 @@ DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
&DaemonLocalServerConnection::readData); &DaemonLocalServerConnection::readData);
Daemon* daemon = Daemon::instance(); Daemon* daemon = Daemon::instance();
connect(daemon, &Daemon::connected, this, connect(daemon, &Daemon::tunnelConnected,
&DaemonLocalServerConnection::connected); this, &DaemonLocalServerConnection::onTunnelConnected);
connect(daemon, &Daemon::tunnelHandshakeFailed,
this, &DaemonLocalServerConnection::onTunnelHandshakeFailed);
connect(daemon, &Daemon::disconnected, this, connect(daemon, &Daemon::disconnected, this,
&DaemonLocalServerConnection::disconnected); &DaemonLocalServerConnection::disconnected);
connect(daemon, &Daemon::backendFailure, this, connect(daemon, &Daemon::backendFailure, this,
@@ -107,19 +109,44 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
InterfaceConfig config; InterfaceConfig config;
if (!Daemon::parseConfig(obj, config)) { if (!Daemon::parseConfig(obj, config)) {
logger.error() << "Invalid configuration"; logger.error() << "Invalid configuration";
emit disconnected(); disconnected();
return; return;
} }
if (!Daemon::instance()->activate(config.m_ifname, config)) {
if (!Daemon::instance()->activate(config)) {
logger.error() << "Failed to activate the interface"; logger.error() << "Failed to activate the interface";
emit disconnected(); disconnected();
} }
return; return;
} }
if (type == "deactivate") { if (type == "deactivate") {
const QString ifname = obj.value("ifname").toString();
if (!ifname.isEmpty()) {
Daemon::instance()->deactivateTunnel(ifname);
} else {
Daemon::instance()->deactivate(true); Daemon::instance()->deactivate(true);
}
return;
}
if (type == "setPrimary") {
InterfaceConfig config;
if (!Daemon::parseConfig(obj, config)) {
logger.error() << "setPrimary: invalid configuration";
return;
}
if (!Daemon::instance()->setPrimary(config.m_ifname, config)) {
logger.error() << "setPrimary failed";
QJsonObject reply;
reply.insert("type", "primaryFailed");
reply.insert("ifname", config.m_ifname);
write(reply);
return;
}
QJsonObject reply;
reply.insert("type", "primaryReady");
reply.insert("ifname", config.m_ifname);
write(reply);
return; return;
} }
@@ -146,10 +173,19 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
logger.warning() << "Invalid command:" << type; logger.warning() << "Invalid command:" << type;
} }
void DaemonLocalServerConnection::connected(const QString& pubkey) { void DaemonLocalServerConnection::onTunnelConnected(const QString& ifname,
const QString& pubkey) {
QJsonObject obj; QJsonObject obj;
obj.insert("type", "connected"); obj.insert("type", "connected");
obj.insert("pubkey", QJsonValue(pubkey)); obj.insert("ifname", ifname);
obj.insert("pubkey", pubkey);
write(obj);
}
void DaemonLocalServerConnection::onTunnelHandshakeFailed(const QString& ifname) {
QJsonObject obj;
obj.insert("type", "disconnected");
obj.insert("ifname", ifname);
write(obj); write(obj);
} }
+3 -1
View File
@@ -6,6 +6,7 @@
#define DAEMONLOCALSERVERCONNECTION_H #define DAEMONLOCALSERVERCONNECTION_H
#include <QObject> #include <QObject>
#include <QString>
#include "daemonerrors.h" #include "daemonerrors.h"
@@ -23,7 +24,8 @@ class DaemonLocalServerConnection final : public QObject {
void parseCommand(const QByteArray& json); void parseCommand(const QByteArray& json);
void connected(const QString& pubkey); void onTunnelConnected(const QString& ifname, const QString& pubkey);
void onTunnelHandshakeFailed(const QString& ifname);
void disconnected(); void disconnected();
void backendFailure(DaemonError err); void backendFailure(DaemonError err);
+2
View File
@@ -62,6 +62,8 @@ QJsonObject InterfaceConfig::toJson() const {
} }
json.insert("vpnDisabledApps", disabledApps); json.insert("vpnDisabledApps", disabledApps);
json.insert("ifname", m_ifname);
return json; return json;
} }
+4
View File
@@ -13,6 +13,8 @@
class QJsonObject; class QJsonObject;
constexpr int ACTIVATION_TIMEOUT_MSEC = 30000;
class InterfaceConfig { class InterfaceConfig {
Q_GADGET Q_GADGET
@@ -57,6 +59,8 @@ class InterfaceConfig {
QString m_underloadPacketMagicHeader; QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader; QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk; QMap<QString, QString> m_specialJunk;
QString m_ifname;
bool m_deferAddressSetup = false;
QJsonObject toJson() const; QJsonObject toJson() const;
QString toWgConf( QString toWgConf(
+1 -3
View File
@@ -14,8 +14,6 @@
#include "interfaceconfig.h" #include "interfaceconfig.h"
constexpr const char* WG_INTERFACE = "amn0";
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60; constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
class WireguardUtils : public QObject { class WireguardUtils : public QObject {
@@ -35,7 +33,7 @@ class WireguardUtils : public QObject {
virtual ~WireguardUtils() = default; virtual ~WireguardUtils() = default;
virtual bool interfaceExists() = 0; virtual bool interfaceExists() = 0;
virtual QString interfaceName() { return WG_INTERFACE; } virtual QString interfaceName() = 0;
virtual bool addInterface(const InterfaceConfig& config) = 0; virtual bool addInterface(const InterfaceConfig& config) = 0;
virtual bool deleteInterface() = 0; virtual bool deleteInterface() = 0;
+1
View File
@@ -1,5 +1,6 @@
#include <QDebug> #include <QDebug>
#include <QTimer> #include <QTimer>
#include <QLocalSocket>
#include <libssh/libssh.h> #include <libssh/libssh.h>
#include "amneziaApplication.h" #include "amneziaApplication.h"
+5 -1
View File
@@ -44,6 +44,8 @@ class ControllerImpl : public QObject {
// "disconnecting" state until the "disconnected" signal is received. // "disconnecting" state until the "disconnected" signal is received.
virtual void deactivate() = 0; virtual void deactivate() = 0;
virtual void setPrimary(const QJsonObject& config) { Q_UNUSED(config) }
// This method is used to retrieve the VPN tunnel status (mainly the number // This method is used to retrieve the VPN tunnel status (mainly the number
// of bytes sent and received). It's called always when the VPN tunnel is // of bytes sent and received). It's called always when the VPN tunnel is
// active. // active.
@@ -71,11 +73,13 @@ class ControllerImpl : public QObject {
void initialized(bool status, bool connected, void initialized(bool status, bool connected,
const QDateTime& connectionDate); const QDateTime& connectionDate);
// These 2 signals can be dispatched at any time.
void connected(const QString& pubkey, void connected(const QString& pubkey,
const QDateTime& connectionTimestamp = QDateTime()); const QDateTime& connectionTimestamp = QDateTime());
void disconnected(); void disconnected();
void primaryReady();
void primaryFailed();
// This method should be emitted after a checkStatus() call. // This method should be emitted after a checkStatus() call.
// "serverIpv4Gateway" is the current VPN tunnel gateway. // "serverIpv4Gateway" is the current VPN tunnel gateway.
// "deviceIpv4Address" is the address of the VPN client. // "deviceIpv4Address" is the address of the VPN client.
+42 -5
View File
@@ -39,7 +39,8 @@ namespace {
Logger logger("LocalSocketController"); Logger logger("LocalSocketController");
} }
LocalSocketController::LocalSocketController() { LocalSocketController::LocalSocketController(const QString& ifname)
: m_ifname(ifname) {
MZ_COUNT_CTOR(LocalSocketController); MZ_COUNT_CTOR(LocalSocketController);
m_socket = new QLocalSocket(this); m_socket = new QLocalSocket(this);
@@ -121,7 +122,8 @@ void LocalSocketController::daemonConnected() {
checkStatus(); checkStatus();
} }
void LocalSocketController::activate(const QJsonObject &rawConfig) { QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfig,
const QString& ifname) {
QString protocolName = rawConfig.value("protocol").toString(); QString protocolName = rawConfig.value("protocol").toString();
int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
@@ -134,11 +136,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
QJsonObject json; QJsonObject json;
json.insert("type", "activate");
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); // json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey)); json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp));
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // 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. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
@@ -230,7 +230,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses; QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
if (splitTunnelType == 2) { if (splitTunnelType == 2) {
for (auto v : splitTunnelSites) { for (auto v : splitTunnelSites) {
QString ipRange = v.toString(); QString ipRange = v.toString();
@@ -292,6 +291,23 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5)); json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
} }
json.insert("ifname", ifname);
return json;
}
void LocalSocketController::activate(const QJsonObject& rawConfig) {
const QString protocolName = rawConfig.value("protocol").toString();
const QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
json.insert("type", "activate");
write(json);
}
void LocalSocketController::setPrimary(const QJsonObject& rawConfig) {
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
json.insert("type", "setPrimary");
write(json); write(json);
} }
@@ -306,6 +322,7 @@ void LocalSocketController::deactivate() {
QJsonObject json; QJsonObject json;
json.insert("type", "deactivate"); json.insert("type", "deactivate");
json.insert("ifname", m_ifname);
write(json); write(json);
emit disconnected(); emit disconnected();
} }
@@ -471,12 +488,20 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
return; return;
} }
auto belongsToThisTunnel = [this, &obj]() {
const QJsonValue val = obj.value("ifname");
return !val.isString() || val.toString() == m_ifname;
};
if (type == "disconnected") { if (type == "disconnected") {
if (!belongsToThisTunnel()) return;
disconnectInternal(); disconnectInternal();
return; return;
} }
if (type == "connected") { if (type == "connected") {
if (!belongsToThisTunnel()) return;
QJsonValue pubkey = obj.value("pubkey"); QJsonValue pubkey = obj.value("pubkey");
if (!pubkey.isString()) { if (!pubkey.isString()) {
logger.error() << "Unexpected pubkey value"; logger.error() << "Unexpected pubkey value";
@@ -494,6 +519,18 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
return; return;
} }
if (type == "primaryReady") {
if (!belongsToThisTunnel()) return;
emit primaryReady();
return;
}
if (type == "primaryFailed") {
if (!belongsToThisTunnel()) return;
emit primaryFailed();
return;
}
if (type == "backendFailure") { if (type == "backendFailure") {
if (!obj.contains("errorCode")) { if (!obj.contains("errorCode")) {
// report a generic error if we dont know what it is. // report a generic error if we dont know what it is.
+8 -1
View File
@@ -19,7 +19,7 @@ class LocalSocketController final : public ControllerImpl {
Q_DISABLE_COPY_MOVE(LocalSocketController) Q_DISABLE_COPY_MOVE(LocalSocketController)
public: public:
LocalSocketController(); explicit LocalSocketController(const QString& ifname);
~LocalSocketController(); ~LocalSocketController();
void initialize(const Device* device, const Keys* keys) override; void initialize(const Device* device, const Keys* keys) override;
@@ -28,6 +28,8 @@ class LocalSocketController final : public ControllerImpl {
void deactivate() override; void deactivate() override;
void setPrimary(const QJsonObject& rawConfig) override;
void checkStatus() override; void checkStatus() override;
void getBackendLogs(std::function<void(const QString&)>&& callback) override; void getBackendLogs(std::function<void(const QString&)>&& callback) override;
@@ -36,6 +38,10 @@ class LocalSocketController final : public ControllerImpl {
bool multihopSupported() override { return true; } bool multihopSupported() override { return true; }
public:
static QJsonObject buildActivateJson(const QJsonObject& rawConfig,
const QString& ifname);
private: private:
void initializeInternal(); void initializeInternal();
void disconnectInternal(); void disconnectInternal();
@@ -59,6 +65,7 @@ class LocalSocketController final : public ControllerImpl {
QByteArray m_buffer; QByteArray m_buffer;
QString m_ifname;
QString m_deviceIpv4; QString m_deviceIpv4;
std::function<void(const QString&)> m_logCallback = nullptr; std::function<void(const QString&)> m_logCallback = nullptr;
@@ -47,7 +47,7 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
// Setup the interface to interact with // Setup the interface to interact with
struct ifreq ifr; struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ); strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
// MTU // MTU
// FIXME: We need to know how many layers deep this particular // FIXME: We need to know how many layers deep this particular
@@ -76,7 +76,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr; struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
// Name the interface and set family // Name the interface and set family
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ); strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET; ifr.ifr_addr.sa_family = AF_INET;
// Get the device address to add to interface // Get the device address to add to interface
@@ -126,7 +126,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
// Get the index of named ifr and link with ifr6 // Get the index of named ifr and link with ifr6
struct ifreq ifr; struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ); strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET6; ifr.ifr_addr.sa_family = AF_INET6;
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr); int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
if (ret) { if (ret) {
@@ -28,7 +28,6 @@ LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
logger.debug() << "Daemon created"; logger.debug() << "Daemon created";
m_wgutils = new WireguardUtilsLinux(this);
m_dnsutils = new DnsUtilsLinux(this); m_dnsutils = new DnsUtilsLinux(this);
m_iputils = new IPUtilsLinux(this); m_iputils = new IPUtilsLinux(this);
@@ -50,3 +49,4 @@ LinuxDaemon* LinuxDaemon::instance() {
Q_ASSERT(s_daemon); Q_ASSERT(s_daemon);
return s_daemon; return s_daemon;
} }
+4 -4
View File
@@ -12,8 +12,6 @@
#include "wireguardutilslinux.h" #include "wireguardutilslinux.h"
class LinuxDaemon final : public Daemon { class LinuxDaemon final : public Daemon {
friend class IPUtilsMacos;
public: public:
LinuxDaemon(); LinuxDaemon();
~LinuxDaemon(); ~LinuxDaemon();
@@ -21,13 +19,15 @@ class LinuxDaemon final : public Daemon {
static LinuxDaemon* instance(); static LinuxDaemon* instance();
protected: protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; } bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; } IPUtils* iputils() override { return m_iputils; }
WireguardUtils* createWgUtils() override {
return new WireguardUtilsLinux(this);
}
private: private:
WireguardUtilsLinux* m_wgutils = nullptr;
DnsUtilsLinux* m_dnsutils = nullptr; DnsUtilsLinux* m_dnsutils = nullptr;
IPUtilsLinux* m_iputils = nullptr; IPUtilsLinux* m_iputils = nullptr;
}; };
@@ -193,8 +193,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
QStringList result; QStringList result;
for (const QString& server : servers) for (const QString& server : servers)
{ {
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o amn+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o amn+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
@@ -278,7 +278,7 @@ void LinuxFirewall::install()
}); });
installAnchor(Both, QStringLiteral("200.allowVPN"), { installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"), QStringLiteral("-o amn+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"), QStringLiteral("-o tun0+ -j ACCEPT"),
QStringLiteral("-o tun2+ -j ACCEPT"), QStringLiteral("-o tun2+ -j ACCEPT"),
}); });
@@ -37,33 +37,6 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
bool allowNets;
bool blockNets;
};
class LinuxFirewall class LinuxFirewall
{ {
public: public:
@@ -39,8 +39,6 @@ typedef struct wg_allowedip {
struct wg_allowedip *next_allowedip; struct wg_allowedip *next_allowedip;
} wg_allowedip; } wg_allowedip;
constexpr const char* WG_INTERFACE = "amn0";
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen, static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
int attrtype, const void* attrdata, int attrtype, const void* attrdata,
size_t attrlen); size_t attrlen);
@@ -55,6 +53,8 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
MZ_COUNT_CTOR(LinuxRouteMonitor); MZ_COUNT_CTOR(LinuxRouteMonitor);
logger.debug() << "LinuxRouteMonitor created."; logger.debug() << "LinuxRouteMonitor created.";
m_physicalGateway = NetworkUtilities::getGatewayAndIface().first;
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (m_nlsock < 0) { if (m_nlsock < 0) {
logger.warning() << "Failed to create netlink socket:" << strerror(errno); logger.warning() << "Failed to create netlink socket:" << strerror(errno);
@@ -63,7 +63,7 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
struct sockaddr_nl nladdr; struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr)); memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK; nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid(); nladdr.nl_pid = 0;
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) { if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
logger.warning() << "Failed to bind netlink socket:" << strerror(errno); logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
} }
@@ -153,7 +153,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
} }
if (rtm->rtm_type == RTN_UNICAST) { if (rtm->rtm_type == RTN_UNICAST) {
int index = if_nametoindex(WG_INTERFACE); int index = if_nametoindex(m_ifname.toUtf8().constData());
if (index <= 0) { if (index <= 0) {
logger.error() << "if_nametoindex() failed:" << strerror(errno); logger.error() << "if_nametoindex() failed:" << strerror(errno);
@@ -164,14 +164,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
} }
if (rtm->rtm_type == RTN_THROW) { if (rtm->rtm_type == RTN_THROW) {
QString gateway = NetworkUtilities::getGatewayAndIface().first; if (action == RTM_NEWROUTE) {
if (gateway.isEmpty()) { if (m_physicalGateway.isEmpty()) {
logger.warning() << "No default gateway available, skipping exclusion route"; logger.warning() << "No physical gateway available, skipping exclusion route";
return false; return false;
} }
struct in_addr ip4; struct in_addr ip4;
inet_pton(AF_INET, gateway.toUtf8(), &ip4); inet_pton(AF_INET, m_physicalGateway.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
}
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST; rtm->rtm_type = RTN_UNICAST;
} }
@@ -32,6 +32,7 @@ class LinuxRouteMonitor final : public QObject {
bool rtmSendRoute(int action, int flags, int type, bool rtmSendRoute(int action, int flags, int type,
const IPAddress& prefix); const IPAddress& prefix);
QString m_ifname; QString m_ifname;
QString m_physicalGateway;
unsigned int m_ifindex = 0; unsigned int m_ifindex = 0;
int m_nlsock = -1; int m_nlsock = -1;
int m_nlseq = 0; int m_nlseq = 0;
@@ -8,17 +8,15 @@
#include <QByteArray> #include <QByteArray>
#include <QDir> #include <QDir>
#include <QElapsedTimer>
#include <QFile> #include <QFile>
#include <QLocalSocket> #include <QLocalSocket>
#include <QTimer> #include <QTimer>
#include <QThread> #include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
#include "killswitch.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
@@ -59,19 +57,20 @@ void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
} }
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
Q_UNUSED(config);
if (m_tunnel.state() != QProcess::NotRunning) { if (m_tunnel.state() != QProcess::NotRunning) {
logger.warning() << "Unable to start: tunnel process already running"; logger.warning() << "Unable to start: tunnel process already running";
return false; return false;
} }
const QString ifname = config.m_ifname;
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
if (!wgRuntimeDir.exists()) { if (!wgRuntimeDir.exists()) {
wgRuntimeDir.mkpath("."); wgRuntimeDir.mkpath(".");
} }
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".sock"); QString wgNameFile = wgRuntimeDir.filePath(ifname + ".sock");
pe.insert("WG_TUN_NAME_FILE", wgNameFile); pe.insert("WG_TUN_NAME_FILE", wgNameFile);
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
pe.insert("LOG_LEVEL", "debug"); pe.insert("LOG_LEVEL", "debug");
@@ -79,7 +78,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
m_tunnel.setProcessEnvironment(pe); m_tunnel.setProcessEnvironment(pe);
QDir appPath(QCoreApplication::applicationDirPath()); QDir appPath(QCoreApplication::applicationDirPath());
QStringList wgArgs = {"-f", "amn0"}; QStringList wgArgs = {"-f", ifname};
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs); m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) { if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "Unable to start tunnel process due to timeout"; logger.error() << "Unable to start tunnel process due to timeout";
@@ -147,29 +146,6 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
if (config.m_killSwitchEnabled) {
FirewallParams params { };
params.dnsServers.append(config.m_primaryDnsServer);
if (!config.m_secondaryDnsServer.isEmpty()) {
params.dnsServers.append(config.m_secondaryDnsServer);
}
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
}
} }
return (err == 0); return (err == 0);
@@ -194,10 +170,8 @@ bool WireguardUtilsLinux::deleteInterface() {
// Garbage collect. // Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); QFile::remove(wgRuntimeDir.filePath(m_ifname + ".name"));
// double-check + ensure our firewall is installed and enabled
KillSwitch::instance()->disableKillSwitch();
return true; return true;
} }
@@ -234,13 +208,6 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
out << "allowed_ip=" << ip.toString() << "\n"; out << "allowed_ip=" << ip.toString() << "\n";
} }
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Peer configuration failed:" << strerror(err); logger.error() << "Peer configuration failed:" << strerror(err);
@@ -252,13 +219,6 @@ bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey = QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString message; QString message;
QTextStream out(&message); QTextStream out(&message);
out << "set=1\n"; out << "set=1\n";
@@ -338,7 +298,7 @@ bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
return false; return false;
} }
if (prefix.prefixLength() > 0) { if (prefix.prefixLength() > 0) {
return m_rtmonitor->insertRoute(prefix); return m_rtmonitor->deleteRoute(prefix);
} }
// Ensure that we do not replace the default route. // Ensure that we do not replace the default route.
@@ -389,13 +349,9 @@ bool WireguardUtilsLinux::excludeLocalNetworks(const QList<IPAddress>& routes) {
QString WireguardUtilsLinux::uapiCommand(const QString& command) { QString WireguardUtilsLinux::uapiCommand(const QString& command) {
QLocalSocket socket; QLocalSocket socket;
QTimer uapiTimeout;
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock"); QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
uapiTimeout.setSingleShot(true);
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite); socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) { if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "QLocalSocket::waitForConnected() failed:" logger.error() << "QLocalSocket::waitForConnected() failed:"
@@ -410,13 +366,15 @@ QString WireguardUtilsLinux::uapiCommand(const QString& command) {
} }
socket.write(message); socket.write(message);
QElapsedTimer elapsed;
elapsed.start();
QByteArray reply; QByteArray reply;
while (!reply.contains("\n\n")) { while (!reply.contains("\n\n")) {
if (!uapiTimeout.isActive()) { const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
logger.error() << "UAPI command timed out"; logger.error() << "UAPI command timed out";
return QString(); return QString();
} }
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
reply.append(socket.readAll()); reply.append(socket.readAll());
} }
@@ -442,45 +400,15 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
timeout.setSingleShot(true); timeout.setSingleShot(true);
timeout.start(WG_TUN_PROC_TIMEOUT); timeout.start(WG_TUN_PROC_TIMEOUT);
QFile file(filename);
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) { while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100); QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
QString ifname = "amn0";
// Test-connect to the UAPI socket.
QLocalSocket sock; QLocalSocket sock;
QDir wgRuntimeDir(WG_RUNTIME_DIR); sock.connectToServer(filename, QIODevice::ReadWrite);
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
sock.connectToServer(sockName, QIODevice::ReadWrite);
if (sock.waitForConnected(100)) { if (sock.waitForConnected(100)) {
return ifname; return QFileInfo(filename).baseName();
} }
} }
return QString(); return QString();
} }
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
{
// double-check + ensure our firewall is installed and enabled
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
// Note: rule precedence is handled inside IpTablesFirewall
LinuxFirewall::ensureRootAnchorPriority();
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
LinuxFirewall::updateAllowNets(params.allowAddrs);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
LinuxFirewall::updateBlockNets(params.blockAddrs);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
LinuxFirewall::updateDNSServers(params.dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
}
@@ -11,7 +11,6 @@
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h" #include "linuxroutemonitor.h"
#include "linuxfirewall.h"
class WireguardUtilsLinux final : public WireguardUtils { class WireguardUtilsLinux final : public WireguardUtils {
@@ -40,7 +39,6 @@ public:
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override; bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -185,6 +185,9 @@ bool DnsUtilsMacos::restoreResolvers() {
} }
void DnsUtilsMacos::backupService(const QString& uuid) { void DnsUtilsMacos::backupService(const QString& uuid) {
if (m_prevServices.contains(uuid)) {
return;
}
DnsBackup backup; DnsBackup backup;
CFStringRef path = CFStringCreateWithFormat( CFStringRef path = CFStringCreateWithFormat(
kCFAllocatorSystemDefault, nullptr, kCFAllocatorSystemDefault, nullptr,
+18 -6
View File
@@ -39,8 +39,12 @@ bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
} }
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) { bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config); WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName(); if (!wg) {
logger.error() << "No wireguard interface for" << config.m_ifname;
return false;
}
QString ifname = wg->interfaceName();
struct ifreq ifr; struct ifreq ifr;
// Create socket file descriptor to perform the ioctl operations on // Create socket file descriptor to perform the ioctl operations on
@@ -80,8 +84,12 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
} }
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) { bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
Q_UNUSED(config); WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName(); if (!wg) {
logger.error() << "No wireguard interface for" << config.m_ifname;
return false;
}
QString ifname = wg->interfaceName();
struct ifaliasreq ifr; struct ifaliasreq ifr;
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr; struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask; struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
@@ -130,8 +138,12 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
} }
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) { bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
Q_UNUSED(config); WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName(); if (!wg) {
logger.error() << "No wireguard interface for" << config.m_ifname;
return false;
}
QString ifname = wg->interfaceName();
struct in6_aliasreq ifr6; struct in6_aliasreq ifr6;
// Name the interface and set family // Name the interface and set family
@@ -28,7 +28,6 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
logger.debug() << "Daemon created"; logger.debug() << "Daemon created";
m_wgutils = new WireguardUtilsMacos(this);
m_dnsutils = new DnsUtilsMacos(this); m_dnsutils = new DnsUtilsMacos(this);
m_iputils = new IPUtilsMacos(this); m_iputils = new IPUtilsMacos(this);
@@ -50,3 +49,4 @@ MacOSDaemon* MacOSDaemon::instance() {
Q_ASSERT(s_daemon); Q_ASSERT(s_daemon);
return s_daemon; return s_daemon;
} }
+4 -4
View File
@@ -11,8 +11,6 @@
#include "wireguardutilsmacos.h" #include "wireguardutilsmacos.h"
class MacOSDaemon final : public Daemon { class MacOSDaemon final : public Daemon {
friend class IPUtilsMacos;
public: public:
MacOSDaemon(); MacOSDaemon();
~MacOSDaemon(); ~MacOSDaemon();
@@ -20,13 +18,15 @@ class MacOSDaemon final : public Daemon {
static MacOSDaemon* instance(); static MacOSDaemon* instance();
protected: protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
bool supportIPUtils() const override { return true; } bool supportIPUtils() const override { return true; }
IPUtils* iputils() override { return m_iputils; } IPUtils* iputils() override { return m_iputils; }
WireguardUtils* createWgUtils() override {
return new WireguardUtilsMacos(this);
}
private: private:
WireguardUtilsMacos* m_wgutils = nullptr;
DnsUtilsMacos* m_dnsutils = nullptr; DnsUtilsMacos* m_dnsutils = nullptr;
IPUtilsMacos* m_iputils = nullptr; IPUtilsMacos* m_iputils = nullptr;
}; };
@@ -36,35 +36,6 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool blockNets;
bool allowNets;
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
};
class MacOSFirewall class MacOSFirewall
{ {
@@ -51,7 +51,6 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
MacosRouteMonitor::~MacosRouteMonitor() { MacosRouteMonitor::~MacosRouteMonitor() {
MZ_COUNT_DTOR(MacosRouteMonitor); MZ_COUNT_DTOR(MacosRouteMonitor);
flushExclusionRoutes();
if (m_rtsock >= 0) { if (m_rtsock >= 0) {
close(m_rtsock); close(m_rtsock);
} }
@@ -436,8 +435,16 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
return true; return true;
} }
if ((action == RTM_ADD) && (errno == EEXIST)) { if ((action == RTM_ADD) && (errno == EEXIST)) {
rtm->rtm_type = RTM_DELETE;
rtm->rtm_seq = m_rtseq++;
write(m_rtsock, rtm, rtm->rtm_msglen);
rtm->rtm_type = RTM_ADD;
rtm->rtm_seq = m_rtseq++;
len = write(m_rtsock, rtm, rtm->rtm_msglen);
if (len == rtm->rtm_msglen) {
return true; return true;
} }
}
if ((action == RTM_DELETE) && (errno == ESRCH)) { if ((action == RTM_DELETE) && (errno == ESRCH)) {
return true; return true;
} }
@@ -9,6 +9,7 @@
#include <QByteArray> #include <QByteArray>
#include <QDir> #include <QDir>
#include <QElapsedTimer>
#include <QFile> #include <QFile>
#include <QLocalSocket> #include <QLocalSocket>
#include <QTimer> #include <QTimer>
@@ -16,8 +17,6 @@
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
#include "killswitch.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
@@ -58,19 +57,20 @@ void WireguardUtilsMacos::tunnelErrorOccurred(QProcess::ProcessError error) {
} }
bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
Q_UNUSED(config);
if (m_tunnel.state() != QProcess::NotRunning) { if (m_tunnel.state() != QProcess::NotRunning) {
logger.warning() << "Unable to start: tunnel process already running"; logger.warning() << "Unable to start: tunnel process already running";
return false; return false;
} }
const QString ifname = config.m_ifname;
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
if (!wgRuntimeDir.exists()) { if (!wgRuntimeDir.exists()) {
wgRuntimeDir.mkpath("."); wgRuntimeDir.mkpath(".");
} }
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"); QString wgNameFile = wgRuntimeDir.filePath(ifname + ".name");
pe.insert("WG_TUN_NAME_FILE", wgNameFile); pe.insert("WG_TUN_NAME_FILE", wgNameFile);
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
pe.insert("LOG_LEVEL", "debug"); pe.insert("LOG_LEVEL", "debug");
@@ -92,6 +92,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
m_tunnel.kill(); m_tunnel.kill();
return false; return false;
} }
QFile::remove(wgNameFile);
logger.debug() << "Created wireguard interface" << m_ifname; logger.debug() << "Created wireguard interface" << m_ifname;
// Start the routing table monitor. // Start the routing table monitor.
@@ -145,30 +146,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
if (config.m_killSwitchEnabled) {
FirewallParams params { };
params.dnsServers.append(config.m_primaryDnsServer);
if (!config.m_secondaryDnsServer.isEmpty()) {
params.dnsServers.append(config.m_secondaryDnsServer);
}
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
}
} }
return (err == 0); return (err == 0);
} }
@@ -190,13 +167,6 @@ bool WireguardUtilsMacos::deleteInterface() {
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT); m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
} }
// Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
KillSwitch::instance()->disableKillSwitch();
return true; return true;
} }
@@ -234,13 +204,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
out << "allowed_ip=" << ip.toString() << "\n"; out << "allowed_ip=" << ip.toString() << "\n";
} }
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Peer configuration failed:" << strerror(err); logger.error() << "Peer configuration failed:" << strerror(err);
@@ -252,13 +215,6 @@ bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey = QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString message; QString message;
QTextStream out(&message); QTextStream out(&message);
out << "set=1\n"; out << "set=1\n";
@@ -389,13 +345,9 @@ bool WireguardUtilsMacos::excludeLocalNetworks(const QList<IPAddress>& routes) {
QString WireguardUtilsMacos::uapiCommand(const QString& command) { QString WireguardUtilsMacos::uapiCommand(const QString& command) {
QLocalSocket socket; QLocalSocket socket;
QTimer uapiTimeout;
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock"); QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
uapiTimeout.setSingleShot(true);
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite); socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) { if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "QLocalSocket::waitForConnected() failed:" logger.error() << "QLocalSocket::waitForConnected() failed:"
@@ -410,13 +362,15 @@ QString WireguardUtilsMacos::uapiCommand(const QString& command) {
} }
socket.write(message); socket.write(message);
QElapsedTimer elapsed;
elapsed.start();
QByteArray reply; QByteArray reply;
while (!reply.contains("\n\n")) { while (!reply.contains("\n\n")) {
if (!uapiTimeout.isActive()) { const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
logger.error() << "UAPI command timed out"; logger.error() << "UAPI command timed out";
return QString(); return QString();
} }
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
reply.append(socket.readAll()); reply.append(socket.readAll());
} }
@@ -463,28 +417,3 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
return QString(); return QString();
} }
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
{
// double-check + ensure our firewall is installed and enabled. This is necessary as
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
MacOSFirewall::ensureRootAnchorPriority();
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
QStringLiteral("allownets"), params.allowAddrs);
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
QStringLiteral("blocknets"), params.blockAddrs);
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
}
@@ -10,7 +10,6 @@
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "macosroutemonitor.h" #include "macosroutemonitor.h"
#include "macosfirewall.h"
class WireguardUtilsMacos final : public WireguardUtils { class WireguardUtilsMacos final : public WireguardUtils {
Q_OBJECT Q_OBJECT
@@ -38,8 +37,6 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override; bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -35,14 +35,8 @@ WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
m_firewallManager = WindowsFirewall::create(this); m_firewallManager = WindowsFirewall::create(this);
Q_ASSERT(m_firewallManager != nullptr); Q_ASSERT(m_firewallManager != nullptr);
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
m_dnsutils = new DnsUtilsWindows(this); m_dnsutils = new DnsUtilsWindows(this);
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager); m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
&WindowsDaemon::monitorBackendFailure);
connect(this, &WindowsDaemon::activationFailure,
[this]() { m_firewallManager->disableKillSwitch(); });
} }
WindowsDaemon::~WindowsDaemon() { WindowsDaemon::~WindowsDaemon() {
@@ -112,3 +106,11 @@ void WindowsDaemon::monitorBackendFailure() {
emit backendFailure(); emit backendFailure();
deactivate(); deactivate();
} }
WireguardUtils* WindowsDaemon::createWgUtils() {
auto utils = WireguardUtilsWindows::create(m_firewallManager, this);
if (!utils) return nullptr;
connect(utils.get(), &WireguardUtilsWindows::backendFailure, this,
&WindowsDaemon::monitorBackendFailure);
return utils.release();
}
@@ -28,8 +28,8 @@ class WindowsDaemon final : public Daemon {
protected: protected:
bool run(Op op, const InterfaceConfig& config) override; bool run(Op op, const InterfaceConfig& config) override;
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
DnsUtils* dnsutils() override { return m_dnsutils; } DnsUtils* dnsutils() override { return m_dnsutils; }
WireguardUtils* createWgUtils() override;
private: private:
void monitorBackendFailure(); void monitorBackendFailure();
@@ -42,7 +42,6 @@ class WindowsDaemon final : public Daemon {
int m_inetAdapterIndex = -1; int m_inetAdapterIndex = -1;
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
DnsUtilsWindows* m_dnsutils = nullptr; DnsUtilsWindows* m_dnsutils = nullptr;
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager; std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
QPointer<WindowsFirewall> m_firewallManager; QPointer<WindowsFirewall> m_firewallManager;
@@ -37,11 +37,14 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
QCoreApplication::setApplicationName("Amnezia VPN Tunnel"); QCoreApplication::setApplicationName("Amnezia VPN Tunnel");
QCoreApplication::setApplicationVersion(Constants::versionString()); QCoreApplication::setApplicationVersion(Constants::versionString());
if (tokens.length() != 2) { if (tokens.length() < 2 || tokens.length() > 3) {
logger.error() << "Expected 1 parameter only: the config file."; logger.error() << "Expected: <config> [<ifname>]";
return 1; return 1;
} }
QString maybeConfig = tokens.at(1); QString maybeConfig = tokens.at(1);
QString name = tokens.length() == 3 && !tokens.at(2).isEmpty()
? tokens.at(2)
: WireguardUtilsWindows::s_defaultInterfaceName();
if (!maybeConfig.startsWith("[Interface]")) { if (!maybeConfig.startsWith("[Interface]")) {
logger.error() << "parameter Does not seem to be a config"; logger.error() << "parameter Does not seem to be a config";
@@ -64,7 +67,6 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function"); WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function");
return 1; return 1;
} }
auto name = WireguardUtilsWindows::s_interfaceName();
if (!tunnelProc(maybeConfig.utf16(), name.utf16())) { if (!tunnelProc(maybeConfig.utf16(), name.utf16())) {
logger.error() << "Failed to activate the tunnel service"; logger.error() << "Failed to activate the tunnel service";
return 1; return 1;
@@ -159,7 +159,7 @@ bool WindowsFirewall::initSublayer() {
return true; return true;
} }
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { bool WindowsFirewall::enableInterface(int vpnAdapterIndex, const QString& ifname) {
// Checks if the FW_Rule was enabled succesfully, // Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not. // disables the whole killswitch and returns false if not.
#define FW_OK(rule) \ #define FW_OK(rule) \
@@ -182,31 +182,39 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
} \ } \
} }
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex; logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex
if (vpnAdapterIndex < 0) << "ifname:" << ifname;
{
QList<uint64_t>& perTunnel = ifname.isEmpty() ? m_globalRules
: m_tunnelRules[ifname];
if (vpnAdapterIndex < 0) {
IPAddress allv4("0.0.0.0/0"); IPAddress allv4("0.0.0.0/0");
if (!blockTrafficTo(allv4, MED_WEIGHT, if (!blockTrafficTo(allv4, MED_WEIGHT, "Block Internet", perTunnel)) {
"Block Internet", "killswitch")) {
return false; return false;
} }
IPAddress allv6("::/0"); IPAddress allv6("::/0");
if (!blockTrafficTo(allv6, MED_WEIGHT, if (!blockTrafficTo(allv6, MED_WEIGHT, "Block Internet", perTunnel)) {
"Block Internet", "killswitch")) {
return false; return false;
} }
} else } else {
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT, FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter")); "Allow usage of VPN Adapter", perTunnel));
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); }
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic"));
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
"Allow all for AmneziaVPN.exe"));
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
"Allow Loopback traffic on device %1"));
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length(); if (m_globalRules.isEmpty()) {
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic", m_globalRules));
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic", m_globalRules));
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
"Allow all for AmneziaVPN.exe", m_globalRules));
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS", m_globalRules));
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
"Allow Loopback traffic on device %1",
m_globalRules));
}
logger.debug() << "Killswitch on! Globals:" << m_globalRules.length()
<< "Tunnel[" << ifname
<< "]:" << m_tunnelRules.value(ifname).length();
return true; return true;
#undef FW_OK #undef FW_OK
} }
@@ -226,7 +234,8 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
// Blocking unprotected traffic // Blocking unprotected traffic
for (const IPAddress& prefix : ranges) { for (const IPAddress& prefix : ranges) {
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) { if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic",
m_globalRules)) {
return false; return false;
} }
} }
@@ -242,7 +251,10 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
} }
// Allow unprotected traffic sent to the following address ranges. // Allow unprotected traffic sent to the following address ranges.
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) { bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString& ifname) {
QList<uint64_t>& target = ifname.isEmpty() ? m_globalRules
: m_tunnelRules[ifname];
// Start the firewall transaction // Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL); auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) { if (result != ERROR_SUCCESS) {
@@ -255,8 +267,9 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
}); });
for (const QString& addr : ranges) { for (const QString& addr : ranges) {
logger.debug() << "Allow killswitch exclude: " << addr; logger.debug() << "Allow killswitch exclude: " << addr << "ifname:" << ifname;
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) { if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT,
"Allow killswitch bypass traffic", target)) {
return false; return false;
} }
} }
@@ -273,6 +286,10 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
QList<uint64_t>& target = config.m_ifname.isEmpty()
? m_globalRules
: m_tunnelRules[config.m_ifname];
// Start the firewall transaction // Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL); auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) { if (result != ERROR_SUCCESS) {
@@ -288,12 +305,12 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
logger.info() << "Enabling traffic for peer" logger.info() << "Enabling traffic for peer"
<< config.m_serverPublicKey; << config.m_serverPublicKey;
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT, if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
"Block Internet", config.m_serverPublicKey)) { "Block Internet", target)) {
return false; return false;
} }
if (!config.m_primaryDnsServer.isEmpty()) { if (!config.m_primaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT, if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) { "Allow DNS-Server", target)) {
return false; return false;
} }
// In some cases, we might configure a 2nd DNS server for IPv6, however // In some cases, we might configure a 2nd DNS server for IPv6, however
@@ -302,7 +319,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) { target)) {
return false; return false;
} }
} }
@@ -310,7 +327,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (!config.m_secondaryDnsServer.isEmpty()) { if (!config.m_secondaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT, if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) { "Allow DNS-Server", target)) {
return false; return false;
} }
// In some cases, we might configure a 2nd DNS server for IPv6, however // In some cases, we might configure a 2nd DNS server for IPv6, however
@@ -319,7 +336,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) { if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) { target)) {
return false; return false;
} }
} }
@@ -328,7 +345,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
for (const QString& dns : config.m_allowedDnsServers) { for (const QString& dns : config.m_allowedDnsServers) {
logger.debug() << "Allow DNS: " << dns; logger.debug() << "Allow DNS: " << dns;
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT, if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) { "Allow DNS-Server", target)) {
return false; return false;
} }
} }
@@ -338,7 +355,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
logger.debug() << "excludedAddresses range: " << i; logger.debug() << "excludedAddresses range: " << i;
if (!allowTrafficTo(i, HIGH_WEIGHT, if (!allowTrafficTo(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) { "Allow Ecxlude route", target)) {
return false; return false;
} }
} }
@@ -354,35 +371,6 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
return true; return true;
} }
bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
auto cleanup = qScopeGuard([&] {
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(m_sessionHandle);
}
});
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
logger.info() << "Disabling traffic for peer" << pubkey;
for (const auto& filterID : m_peerRules.values(pubkey)) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
m_peerRules.remove(pubkey, filterID);
}
// Commit!
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
return true;
}
bool WindowsFirewall::disableKillSwitch() { bool WindowsFirewall::disableKillSwitch() {
return KillSwitch::instance()->disableKillSwitch(); return KillSwitch::instance()->disableKillSwitch();
} }
@@ -400,11 +388,13 @@ bool WindowsFirewall::allowAllTraffic() {
return false; return false;
} }
for (const auto& filterID : m_peerRules.values()) { for (const auto& bucket : qAsConst(m_tunnelRules)) {
for (const auto& filterID : bucket) {
FwpmFilterDeleteById0(m_sessionHandle, filterID); FwpmFilterDeleteById0(m_sessionHandle, filterID);
} }
}
for (const auto& filterID : qAsConst(m_activeRules)) { for (const auto& filterID : qAsConst(m_globalRules)) {
FwpmFilterDeleteById0(m_sessionHandle, filterID); FwpmFilterDeleteById0(m_sessionHandle, filterID);
} }
@@ -415,15 +405,42 @@ bool WindowsFirewall::allowAllTraffic() {
<< result; << result;
return false; return false;
} }
m_peerRules.clear(); m_tunnelRules.clear();
m_activeRules.clear(); m_globalRules.clear();
logger.debug() << "Firewall Disabled!"; logger.debug() << "Firewall Disabled!";
return true; return true;
} }
bool WindowsFirewall::disableKillSwitchForTunnel(const QString& ifname) {
if (ifname.isEmpty() || !m_tunnelRules.contains(ifname)) {
return true;
}
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:" << result;
return false;
}
const QList<uint64_t> filters = m_tunnelRules.take(ifname);
logger.info() << "Disabling killswitch filters for tunnel" << ifname
<< "count:" << filters.length();
for (const auto& filterID : filters) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:" << result;
return false;
}
return true;
}
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath, bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
int weight, int weight,
const QString& title) { const QString& title,
QList<uint64_t>& target) {
DWORD result = ERROR_SUCCESS; DWORD result = ERROR_SUCCESS;
Q_ASSERT(weight <= 15); Q_ASSERT(weight <= 15);
@@ -460,7 +477,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
{ {
QString desc("Permit (out) IPv4 Traffic of: " + appName); QString desc("Permit (out) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, desc)) { if (!enableFilter(&filter, title, desc, target)) {
return false; return false;
} }
} }
@@ -468,7 +485,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
{ {
QString desc("Permit (in) IPv4 Traffic of: " + appName); QString desc("Permit (in) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, desc)) { if (!enableFilter(&filter, title, desc, target)) {
return false; return false;
} }
} }
@@ -476,7 +493,8 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
} }
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight, bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title) { const QString& title,
QList<uint64_t>& target) {
FWPM_FILTER_CONDITION0 conds; FWPM_FILTER_CONDITION0 conds;
// Condition: Request must be targeting the TUN interface // Condition: Request must be targeting the TUN interface
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX; conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
@@ -498,25 +516,25 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
// #1 Permit outbound IPv4 traffic. // #1 Permit outbound IPv4 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) { description.arg("out").arg(networkAdapter), target)) {
return false; return false;
} }
// #2 Permit inbound IPv4 traffic. // #2 Permit inbound IPv4 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("in").arg(networkAdapter))) { description.arg("in").arg(networkAdapter), target)) {
return false; return false;
} }
// #3 Permit outbound IPv6 traffic. // #3 Permit outbound IPv6 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) { description.arg("out").arg(networkAdapter), target)) {
return false; return false;
} }
// #4 Permit inbound IPv6 traffic. // #4 Permit inbound IPv6 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("in").arg(networkAdapter))) { description.arg("in").arg(networkAdapter), target)) {
return false; return false;
} }
return true; return true;
@@ -524,7 +542,7 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight, bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
const QString& title, const QString& title,
const QString& peer) { QList<uint64_t>& target) {
GUID layerKeyOut; GUID layerKeyOut;
GUID layerKeyIn; GUID layerKeyIn;
if (addr.type() == QAbstractSocket::IPv4Protocol) { if (addr.type() == QAbstractSocket::IPv4Protocol) {
@@ -562,11 +580,11 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
// Send the filters down to the firewall. // Send the filters down to the firewall.
QString description = "Permit traffic %1 " + addr.toString(); QString description = "Permit traffic %1 " + addr.toString();
filter.layerKey = layerKeyOut; filter.layerKey = layerKeyOut;
if (!enableFilter(&filter, title, description.arg("to"), peer)) { if (!enableFilter(&filter, title, description.arg("to"), target)) {
return false; return false;
} }
filter.layerKey = layerKeyIn; filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title, description.arg("from"), peer)) { if (!enableFilter(&filter, title, description.arg("from"), target)) {
return false; return false;
} }
return true; return true;
@@ -574,7 +592,7 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port, bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
int weight, const QString& title, int weight, const QString& title,
const QString& peer) { QList<uint64_t>& target) {
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol; bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
GUID layerOut = GUID layerOut =
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6; isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
@@ -623,19 +641,20 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
filter.layerKey = layerOut; filter.layerKey = layerOut;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("to").arg(targetIP.toString()).arg(port), description.arg("to").arg(targetIP.toString()).arg(port),
peer)) { target)) {
return false; return false;
} }
filter.layerKey = layerIn; filter.layerKey = layerIn;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("from").arg(targetIP.toString()).arg(port), description.arg("from").arg(targetIP.toString()).arg(port),
peer)) { target)) {
return false; return false;
} }
return true; return true;
} }
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) { bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target) {
// Allow outbound DHCPv4 // Allow outbound DHCPv4
{ {
FWPM_FILTER_CONDITION0 conds[4]; FWPM_FILTER_CONDITION0 conds[4];
@@ -672,7 +691,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) { if (!enableFilter(&filter, title, "Allow Outbound DHCP", target)) {
return false; return false;
} }
} }
@@ -705,7 +724,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, "Allow inbound DHCP")) { if (!enableFilter(&filter, title, "Allow inbound DHCP", target)) {
return false; return false;
} }
} }
@@ -740,7 +759,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) { if (!enableFilter(&filter, title, "Allow outbound DHCPv6", target)) {
return false; return false;
} }
} }
@@ -773,7 +792,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.weight.uint8 = weight; filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title, "Allow inbound DHCPv6")) { if (!enableFilter(&filter, title, "Allow inbound DHCPv6", target)) {
return false; return false;
} }
} }
@@ -781,7 +800,8 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
} }
// Allows the internal Hyper-V Switches to work. // Allows the internal Hyper-V Switches to work.
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) { bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target) {
FWPM_FILTER_CONDITION0 cond; FWPM_FILTER_CONDITION0 cond;
// Condition: Request must be targeting the TUN interface // Condition: Request must be targeting the TUN interface
cond.fieldKey = FWPM_CONDITION_L2_FLAGS; cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
@@ -801,12 +821,12 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
// #1 Permit Hyper-V => Hyper-V outbound. // #1 Permit Hyper-V => Hyper-V outbound.
filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE; filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE;
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound")) { if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound", target)) {
return false; return false;
} }
// #2 Permit Hyper-V => Hyper-V inbound. // #2 Permit Hyper-V => Hyper-V inbound.
filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE; filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE;
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound")) { if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound", target)) {
return false; return false;
} }
return true; return true;
@@ -814,7 +834,7 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight, bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
const QString& title, const QString& title,
const QString& peer) { QList<uint64_t>& target) {
QString description("Block traffic %1 %2 "); QString description("Block traffic %1 %2 ");
auto lower = addr.address(); auto lower = addr.address();
@@ -852,12 +872,12 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
filter.layerKey = layerKeyOut; filter.layerKey = layerKeyOut;
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()), if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
peer)) { target)) {
return false; return false;
} }
filter.layerKey = layerKeyIn; filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title, if (!enableFilter(&filter, title,
description.arg("from").arg(addr.toString()), peer)) { description.arg("from").arg(addr.toString()), target)) {
return false; return false;
} }
return true; return true;
@@ -865,9 +885,9 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList, bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
uint8_t weight, const QString& title, uint8_t weight, const QString& title,
const QString& peer) { QList<uint64_t>& target) {
for (auto range : rangeList) { for (auto range : rangeList) {
if (!blockTrafficTo(range, weight, title, peer)) { if (!blockTrafficTo(range, weight, title, target)) {
logger.info() << "Setting Range of" << range.toString() << "failed"; logger.info() << "Setting Range of" << range.toString() << "failed";
return false; return false;
} }
@@ -923,7 +943,8 @@ void WindowsFirewall::importAddress(const QHostAddress& addr,
} }
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight, bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
const QString& title) { const QString& title,
QList<uint64_t>& target) {
// Allow Traffic to IP with PORT using any protocol // Allow Traffic to IP with PORT using any protocol
FWPM_FILTER_CONDITION0 conds[3]; FWPM_FILTER_CONDITION0 conds[3];
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL; conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
@@ -953,20 +974,20 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
QString description("Block %1 on Port %2"); QString description("Block %1 on Port %2");
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port))) { if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port), target)) {
return false; return false;
} }
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) { if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port), target)) {
return false; return false;
} }
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) { if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port), target)) {
return false; return false;
} }
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) { if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port), target)) {
return false; return false;
} }
return true; return true;
@@ -974,7 +995,7 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title, bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
const QString& description, const QString& description,
const QString& peer) { QList<uint64_t>& target) {
uint64_t filterID = 0; uint64_t filterID = 0;
auto name = title.toStdWString(); auto name = title.toStdWString();
auto desc = description.toStdWString(); auto desc = description.toStdWString();
@@ -987,16 +1008,12 @@ bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
return false; return false;
} }
logger.info() << "Filter added: " << title << ":" << description; logger.info() << "Filter added: " << title << ":" << description;
if (peer.isEmpty()) { target.append(filterID);
m_activeRules.append(filterID);
} else {
m_peerRules.insert(peer, filterID);
}
return true; return true;
} }
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, const QString& title,
const QString& title) { QList<uint64_t>& target) {
QList<QNetworkInterface> networkInterfaces = QList<QNetworkInterface> networkInterfaces =
QNetworkInterface::allInterfaces(); QNetworkInterface::allInterfaces();
for (const auto& iface : networkInterfaces) { for (const auto& iface : networkInterfaces) {
@@ -1004,7 +1021,7 @@ bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
continue; continue;
} }
if (!allowTrafficOfAdapter(iface.index(), weight, if (!allowTrafficOfAdapter(iface.index(), weight,
title.arg(iface.name()))) { title.arg(iface.name()), target)) {
return false; return false;
} }
} }
@@ -15,6 +15,7 @@
#include <QByteArray> #include <QByteArray>
#include <QHostAddress> #include <QHostAddress>
#include <QMap>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
@@ -38,38 +39,42 @@ class WindowsFirewall final : public QObject {
static WindowsFirewall* create(QObject* parent); static WindowsFirewall* create(QObject* parent);
~WindowsFirewall() override; ~WindowsFirewall() override;
bool enableInterface(int vpnAdapterIndex); bool enableInterface(int vpnAdapterIndex, const QString& ifname = QString());
bool enableLanBypass(const QList<IPAddress>& ranges); bool enableLanBypass(const QList<IPAddress>& ranges);
bool enablePeerTraffic(const InterfaceConfig& config); bool enablePeerTraffic(const InterfaceConfig& config);
bool disablePeerTraffic(const QString& pubkey);
bool disableKillSwitch(); bool disableKillSwitch();
bool disableKillSwitchForTunnel(const QString& ifname);
bool allowAllTraffic(); bool allowAllTraffic();
bool allowTrafficRange(const QStringList& ranges); bool allowTrafficRange(const QStringList& ranges, const QString& ifname = QString());
private: private:
static bool initSublayer(); static bool initSublayer();
WindowsFirewall(HANDLE session, QObject* parent); WindowsFirewall(HANDLE session, QObject* parent);
HANDLE m_sessionHandle; HANDLE m_sessionHandle;
bool m_init = false; bool m_init = false;
QList<uint64_t> m_activeRules; QList<uint64_t> m_globalRules;
QMultiMap<QString, uint64_t> m_peerRules; QMap<QString, QList<uint64_t>> m_tunnelRules;
bool allowTrafficForAppOnAll(const QString& exePath, int weight, bool allowTrafficForAppOnAll(const QString& exePath, int weight,
const QString& title); const QString& title, QList<uint64_t>& target);
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight, bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
const QString& title, const QString& peer = QString()); const QString& title, QList<uint64_t>& target);
bool blockTrafficTo(const IPAddress& addr, uint8_t weight, bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
const QString& title, const QString& peer = QString()); const QString& title, QList<uint64_t>& target);
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title); bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title,
QList<uint64_t>& target);
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title, bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
const QString& peer = QString()); QList<uint64_t>& target);
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight, bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
const QString& title, const QString& peer = QString()); const QString& title, QList<uint64_t>& target);
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight, bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title); const QString& title, QList<uint64_t>& target);
bool allowDHCPTraffic(uint8_t weight, const QString& title); bool allowDHCPTraffic(uint8_t weight, const QString& title,
bool allowHyperVTraffic(uint8_t weight, const QString& title); QList<uint64_t>& target);
bool allowLoopbackTraffic(uint8_t weight, const QString& title); bool allowHyperVTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target);
bool allowLoopbackTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target);
// Utils // Utils
QString getCurrentPath(); QString getCurrentPath();
@@ -78,8 +83,7 @@ class WindowsFirewall final : public QObject {
void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value, void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value,
OUT QByteArray* v6DataBuffer); OUT QByteArray* v6DataBuffer);
bool enableFilter(FWPM_FILTER0* filter, const QString& title, bool enableFilter(FWPM_FILTER0* filter, const QString& title,
const QString& description, const QString& description, QList<uint64_t>& target);
const QString& peer = QString());
}; };
#endif // WINDOWSFIREWALL_H #endif // WINDOWSFIREWALL_H
@@ -58,9 +58,12 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
return 0; return 0;
} }
QSet<quint64> WindowsRouteMonitor::s_vpnLuids;
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent) WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
: QObject(parent), m_luid(luid) { : QObject(parent), m_luid(luid) {
MZ_COUNT_CTOR(WindowsRouteMonitor); MZ_COUNT_CTOR(WindowsRouteMonitor);
s_vpnLuids.insert(luid);
logger.debug() << "WindowsRouteMonitor created."; logger.debug() << "WindowsRouteMonitor created.";
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle); NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
@@ -69,8 +72,8 @@ WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
WindowsRouteMonitor::~WindowsRouteMonitor() { WindowsRouteMonitor::~WindowsRouteMonitor() {
MZ_COUNT_DTOR(WindowsRouteMonitor); MZ_COUNT_DTOR(WindowsRouteMonitor);
CancelMibChangeNotify2(m_routeHandle); CancelMibChangeNotify2(m_routeHandle);
s_vpnLuids.remove(m_luid);
flushRouteTable(m_exclusionRoutes);
flushRouteTable(m_clonedRoutes); flushRouteTable(m_clonedRoutes);
logger.debug() << "WindowsRouteMonitor destroyed."; logger.debug() << "WindowsRouteMonitor destroyed.";
} }
@@ -95,7 +98,8 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
// Rebuild the list of interfaces that are valid for routing. // Rebuild the list of interfaces that are valid for routing.
for (ULONG i = 0; i < table->NumEntries; i++) { for (ULONG i = 0; i < table->NumEntries; i++) {
MIB_IPINTERFACE_ROW* row = &table->Table[i]; MIB_IPINTERFACE_ROW* row = &table->Table[i];
if (row->InterfaceLuid.Value == m_luid) { // Skip any VPN wintun (own or sibling) so exclusion routes never pick one.
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
continue; continue;
} }
if (!row->Connected) { if (!row->Connected) {
@@ -126,8 +130,8 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
nexthop.si_family = data->DestinationPrefix.Prefix.si_family; nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
for (ULONG i = 0; i < table->NumEntries; i++) { for (ULONG i = 0; i < table->NumEntries; i++) {
MIB_IPFORWARD_ROW2* row = &table->Table[i]; MIB_IPFORWARD_ROW2* row = &table->Table[i];
// Ignore routes into the VPN interface. // Skip any VPN wintun (own or sibling).
if (row->InterfaceLuid.Value == m_luid) { if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
continue; continue;
} }
if (row->DestinationPrefix.PrefixLength < bestMatch) { if (row->DestinationPrefix.PrefixLength < bestMatch) {
@@ -239,14 +243,16 @@ QHostAddress WindowsRouteMonitor::prefixToAddress(
} }
} }
bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const { bool WindowsRouteMonitor::isRouteExcluded(void* ptable,
auto i = m_exclusionRoutes.constBegin(); const IP_ADDRESS_PREFIX* dest) const {
while (i != m_exclusionRoutes.constEnd()) { PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
const MIB_IPFORWARD_ROW2* row = i.value(); for (ULONG i = 0; i < table->NumEntries; i++) {
const MIB_IPFORWARD_ROW2* row = &table->Table[i];
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
if (routeContainsDest(&row->DestinationPrefix, dest)) { if (routeContainsDest(&row->DestinationPrefix, dest)) {
return true; return true;
} }
i++;
} }
return false; return false;
} }
@@ -272,8 +278,8 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
for (ULONG i = 0; i < table->NumEntries; i++) { for (ULONG i = 0; i < table->NumEntries; i++) {
MIB_IPFORWARD_ROW2* row = &table->Table[i]; MIB_IPFORWARD_ROW2* row = &table->Table[i];
// Ignore routes into the VPN interface. // Skip any VPN wintun (own or sibling).
if (row->InterfaceLuid.Value == m_luid) { if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
continue; continue;
} }
// Ignore the default route // Ignore the default route
@@ -286,7 +292,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
continue; continue;
} }
// Ignore routes which should be excluded. // Ignore routes which should be excluded.
if (isRouteExcluded(&row->DestinationPrefix)) { if (isRouteExcluded(table, &row->DestinationPrefix)) {
continue; continue;
} }
QHostAddress destination = prefixToAddress(&row->DestinationPrefix); QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
@@ -375,11 +381,6 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
return true; return true;
} }
if (m_exclusionRoutes.contains(prefix)) {
logger.warning() << "Exclusion route already exists";
return false;
}
// Allocate and initialize the MIB routing table row. // Allocate and initialize the MIB routing table row.
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2; MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
InitializeIpForwardEntry(data); InitializeIpForwardEntry(data);
@@ -427,8 +428,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
updateCapturedRoutes(family, table); updateCapturedRoutes(family, table);
updateExclusionRoute(data, table); updateExclusionRoute(data, table);
FreeMibTable(table); FreeMibTable(table);
delete data;
m_exclusionRoutes[prefix] = data; m_ownedExclusionRoutes.insert(prefix);
return true; return true;
} }
@@ -436,23 +437,39 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for" logger.debug() << "Deleting exclusion route for"
<< prefix.address().toString(); << prefix.address().toString();
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix); m_ownedExclusionRoutes.remove(prefix);
if (data == nullptr) {
return true; PMIB_IPFORWARD_TABLE2 table;
DWORD result = GetIpForwardTable2(AF_UNSPEC, &table);
if (result != NO_ERROR) {
logger.error() << "Failed to fetch routing table:" << result;
return false;
} }
DWORD result = DeleteIpForwardEntry2(data); const bool isV4 = prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { const ADDRESS_FAMILY addrFamily =
logger.error() << "Failed to delete route to" isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
<< prefix.toString() : static_cast<ADDRESS_FAMILY>(AF_INET6);
<< "result:" << result; bool deleted = false;
for (ULONG i = 0; i < table->NumEntries; i++) {
MIB_IPFORWARD_ROW2* row = &table->Table[i];
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
DWORD r = DeleteIpForwardEntry2(row);
if (r == NO_ERROR || r == ERROR_NOT_FOUND) {
deleted = true;
} else {
logger.error() << "Failed to delete route to" << prefix.toString()
<< "result:" << r;
} }
break;
// Captured routes might have changed. }
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family); FreeMibTable(table);
updateCapturedRoutes(addrFamily);
delete data; return deleted;
return true;
} }
void WindowsRouteMonitor::flushRouteTable( void WindowsRouteMonitor::flushRouteTable(
@@ -492,8 +509,24 @@ void WindowsRouteMonitor::routeChanged() {
updateInterfaceMetrics(AF_UNSPEC); updateInterfaceMetrics(AF_UNSPEC);
updateCapturedRoutes(AF_UNSPEC, table); updateCapturedRoutes(AF_UNSPEC, table);
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
updateExclusionRoute(data, table); for (const IPAddress& prefix : m_ownedExclusionRoutes) {
const bool isV4 =
prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
const ADDRESS_FAMILY addrFamily =
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
: static_cast<ADDRESS_FAMILY>(AF_INET6);
for (ULONG i = 0; i < table->NumEntries; i++) {
MIB_IPFORWARD_ROW2* row = &table->Table[i];
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
MIB_IPFORWARD_ROW2 copy = *row;
updateExclusionRoute(&copy, table);
break;
}
} }
FreeMibTable(table); FreeMibTable(table);
@@ -14,6 +14,7 @@
#include <QHash> #include <QHash>
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
#include <QSet>
#include "ipaddress.h" #include "ipaddress.h"
@@ -28,7 +29,6 @@ class WindowsRouteMonitor final : public QObject {
bool addExclusionRoute(const IPAddress& prefix); bool addExclusionRoute(const IPAddress& prefix);
bool deleteExclusionRoute(const IPAddress& prefix); bool deleteExclusionRoute(const IPAddress& prefix);
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
quint64 getLuid() const { return m_luid; } quint64 getLuid() const { return m_luid; }
@@ -36,7 +36,7 @@ class WindowsRouteMonitor final : public QObject {
void routeChanged(); void routeChanged();
private: private:
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const; bool isRouteExcluded(void* table, const IP_ADDRESS_PREFIX* dest) const;
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route, static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
const IP_ADDRESS_PREFIX* dest); const IP_ADDRESS_PREFIX* dest);
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest); static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
@@ -47,7 +47,7 @@ class WindowsRouteMonitor final : public QObject {
void updateCapturedRoutes(int family); void updateCapturedRoutes(int family);
void updateCapturedRoutes(int family, void* table); void updateCapturedRoutes(int family, void* table);
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes; QSet<IPAddress> m_ownedExclusionRoutes;
QMap<quint64, ULONG> m_interfaceMetricsIpv4; QMap<quint64, ULONG> m_interfaceMetricsIpv4;
QMap<quint64, ULONG> m_interfaceMetricsIpv6; QMap<quint64, ULONG> m_interfaceMetricsIpv6;
@@ -57,6 +57,8 @@ class WindowsRouteMonitor final : public QObject {
const quint64 m_luid = 0; const quint64 m_luid = 0;
HANDLE m_routeHandle = INVALID_HANDLE_VALUE; HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
static QSet<quint64> s_vpnLuids;
}; };
#endif /* WINDOWSROUTEMONITOR_H */ #endif /* WINDOWSROUTEMONITOR_H */
@@ -15,9 +15,8 @@
#include "platforms/windows/windowsutils.h" #include "platforms/windows/windowsutils.h"
#include "windowsdaemon.h" #include "windowsdaemon.h"
#define TUNNEL_NAMED_PIPE \ #define TUNNEL_NAMED_PIPE_PREFIX \
"\\\\." \ "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\"
"\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\AmneziaVPN"
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000; constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
@@ -28,6 +27,10 @@ Logger logger("WindowsTunnelService");
static bool stopAndDeleteTunnelService(SC_HANDLE service); static bool stopAndDeleteTunnelService(SC_HANDLE service);
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus); static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
std::wstring WindowsTunnelService::serviceNameForIfname(const QString& ifname) {
return (QStringLiteral("AmneziaWGTunnel$") + ifname).toStdWString();
}
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) { WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(WindowsTunnelService); MZ_COUNT_CTOR(WindowsTunnelService);
logger.debug() << "WindowsTunnelService created."; logger.debug() << "WindowsTunnelService created.";
@@ -37,7 +40,7 @@ WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
WindowsUtils::windowsLog("Failed to open SCManager"); WindowsUtils::windowsLog("Failed to open SCManager");
} }
// Is the service already running? Terminate it. // Is the legacy single-tunnel service still around? Terminate it.
SC_HANDLE service = SC_HANDLE service =
OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS); OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
if (service != nullptr) { if (service != nullptr) {
@@ -108,8 +111,11 @@ void WindowsTunnelService::timeout() {
emit backendFailure(); emit backendFailure();
} }
bool WindowsTunnelService::start(const QString& configData) { bool WindowsTunnelService::start(const QString& configData, const QString& ifname) {
logger.debug() << "Starting the tunnel service"; logger.debug() << "Starting the tunnel service for" << ifname;
m_ifname = ifname;
const std::wstring serviceName = serviceNameForIfname(ifname);
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile()); m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
m_logworker->moveToThread(&m_logthread); m_logworker->moveToThread(&m_logthread);
@@ -128,10 +134,9 @@ bool WindowsTunnelService::start(const QString& configData) {
m_logworker = nullptr; m_logworker = nullptr;
}); });
// Let's see if we have to delete a previous instance. service = OpenService(scm, serviceName.c_str(), SERVICE_ALL_ACCESS);
service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
if (service) { if (service) {
logger.debug() << "An existing service has been detected. Let's close it."; logger.debug() << "A stale service was detected. Cleaning it up.";
if (!stopAndDeleteTunnelService(service)) { if (!stopAndDeleteTunnelService(service)) {
return false; return false;
} }
@@ -143,12 +148,12 @@ bool WindowsTunnelService::start(const QString& configData) {
{ {
QTextStream out(&serviceCmdline); QTextStream out(&serviceCmdline);
out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \"" out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \""
<< configData << "\""; << configData << "\" \"" << ifname << "\"";
} }
logger.debug() << "Service:" << qApp->applicationFilePath(); logger.debug() << "Service:" << qApp->applicationFilePath();
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amnezia VPN (tunnel)", service = CreateService(scm, serviceName.c_str(), L"Amnezia VPN (tunnel)",
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0, (const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
@@ -236,8 +241,9 @@ static bool stopAndDeleteTunnelService(SC_HANDLE service) {
} }
QString WindowsTunnelService::uapiCommand(const QString& command) { QString WindowsTunnelService::uapiCommand(const QString& command) {
// Create a pipe to the tunnel service. const std::wstring pipeName = std::wstring(TEXT(TUNNEL_NAMED_PIPE_PREFIX))
LPTSTR tunnelName = (LPTSTR)TEXT(TUNNEL_NAMED_PIPE); + m_ifname.toStdWString();
LPCWSTR tunnelName = pipeName.c_str();
HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, 0, nullptr); OPEN_EXISTING, 0, nullptr);
if (pipe == INVALID_HANDLE_VALUE) { if (pipe == INVALID_HANDLE_VALUE) {
@@ -9,6 +9,7 @@
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include <string>
#include "windowstunnellogger.h" #include "windowstunnellogger.h"
@@ -20,11 +21,13 @@ class WindowsTunnelService final : public QObject {
WindowsTunnelService(QObject* parent = nullptr); WindowsTunnelService(QObject* parent = nullptr);
~WindowsTunnelService(); ~WindowsTunnelService();
bool start(const QString& configData); bool start(const QString& configData, const QString& ifname);
void stop(); void stop();
bool isRunning(); bool isRunning();
QString uapiCommand(const QString& command); QString uapiCommand(const QString& command);
static std::wstring serviceNameForIfname(const QString& ifname);
signals: signals:
void backendFailure(); void backendFailure();
@@ -36,6 +39,7 @@ class WindowsTunnelService final : public QObject {
QTimer m_timer; QTimer m_timer;
QThread m_logthread; QThread m_logthread;
WindowsTunnelLogger* m_logworker = nullptr; WindowsTunnelLogger* m_logworker = nullptr;
QString m_ifname;
// These are really SC_HANDLEs in disguise. // These are really SC_HANDLEs in disguise.
void* m_scm = nullptr; void* m_scm = nullptr;
@@ -102,23 +102,36 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
return false; return false;
} }
// We don't want to pass a peer just yet, that will happen later with
// a UAPI command in WireguardUtilsWindows::updatePeer(), so truncate
// the config file to remove the [Peer] section.
qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive); qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive);
if (peerStart >= 0) { if (peerStart >= 0) {
configString.truncate(peerStart); configString.truncate(peerStart);
} }
if (!m_tunnel.start(configString)) { auto stripLine = [&](const QString& key) {
qsizetype start = configString.startsWith(key + " = ")
? 0
: configString.indexOf("\n" + key + " = ");
if (start < 0) return;
if (start != 0) start += 1;
qsizetype end = configString.indexOf('\n', start);
if (end < 0) return;
configString.remove(start, end - start + 1);
};
stripLine("DNS");
if (config.m_deferAddressSetup) {
// Wintun rejects duplicate IPv4; daemon will assign at swap time.
stripLine("Address");
}
m_ifname = config.m_ifname.isEmpty() ? s_defaultInterfaceName() : config.m_ifname;
if (!m_tunnel.start(configString, m_ifname)) {
logger.error() << "Failed to activate the tunnel service"; logger.error() << "Failed to activate the tunnel service";
return false; return false;
} }
// Determine the interface LUID
NET_LUID luid; NET_LUID luid;
QString ifAlias = interfaceName(); DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)m_ifname.utf16(), &luid);
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)ifAlias.utf16(), &luid);
if (result != 0) { if (result != 0) {
logger.error() << "Failed to lookup LUID:" << result; logger.error() << "Failed to lookup LUID:" << result;
return false; return false;
@@ -126,14 +139,6 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
m_luid = luid.Value; m_luid = luid.Value;
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this); m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
if (config.m_killSwitchEnabled) {
// Enable the windows firewall
NET_IFINDEX ifindex;
ConvertInterfaceLuidToIndex(&luid, &ifindex);
m_firewall->allowAllTraffic();
m_firewall->enableInterface(ifindex);
}
logger.debug() << "Registration completed"; logger.debug() << "Registration completed";
return true; return true;
} }
@@ -143,7 +148,6 @@ bool WireguardUtilsWindows::deleteInterface() {
m_routeMonitor->deleteLater(); m_routeMonitor->deleteLater();
} }
m_firewall->disableKillSwitch();
m_tunnel.stop(); m_tunnel.stop();
return true; return true;
} }
@@ -154,10 +158,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
QByteArray pskKey = QByteArray pskKey =
QByteArray::fromBase64(qPrintable(config.m_serverPskKey)); QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
if (config.m_killSwitchEnabled) {
// Enable the windows firewall for this peer.
m_firewall->enablePeerTraffic(config);
}
logger.debug() << "Configuring peer" << publicKey.toHex() logger.debug() << "Configuring peer" << publicKey.toHex()
<< "via" << config.m_serverIpv4AddrIn; << "via" << config.m_serverIpv4AddrIn;
@@ -185,12 +185,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
out << "allowed_ip=" << ip.toString() << "\n"; out << "allowed_ip=" << ip.toString() << "\n";
} }
// Exclude the server address, except for multihop exit servers.
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString reply = m_tunnel.uapiCommand(message); QString reply = m_tunnel.uapiCommand(message);
logger.debug() << "DATA:" << reply; logger.debug() << "DATA:" << reply;
return true; return true;
@@ -200,15 +194,6 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey = QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
// Disable the windows firewall for this peer.
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
QString message; QString message;
QTextStream out(&message); QTextStream out(&message);
out << "set=1\n"; out << "set=1\n";
@@ -27,10 +27,8 @@ class WireguardUtilsWindows final : public WireguardUtils {
~WireguardUtilsWindows(); ~WireguardUtilsWindows();
bool interfaceExists() override { return m_tunnel.isRunning(); } bool interfaceExists() override { return m_tunnel.isRunning(); }
QString interfaceName() override { QString interfaceName() override { return m_ifname; }
return WireguardUtilsWindows::s_interfaceName(); static const QString s_defaultInterfaceName() { return "AmneziaVPN"; }
}
static const QString s_interfaceName() { return "AmneziaVPN"; }
bool addInterface(const InterfaceConfig& config) override; bool addInterface(const InterfaceConfig& config) override;
bool deleteInterface() override; bool deleteInterface() override;
@@ -54,6 +52,7 @@ class WireguardUtilsWindows final : public WireguardUtils {
void buildMibForwardRow(const IPAddress& prefix, void* row); void buildMibForwardRow(const IPAddress& prefix, void* row);
quint64 m_luid = 0; quint64 m_luid = 0;
QString m_ifname;
WindowsTunnelService m_tunnel; WindowsTunnelService m_tunnel;
QPointer<WindowsRouteMonitor> m_routeMonitor; QPointer<WindowsRouteMonitor> m_routeMonitor;
QPointer<WindowsFirewall> m_firewall; QPointer<WindowsFirewall> m_firewall;
@@ -1,7 +1,8 @@
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\ if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\ elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\ elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\ elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\ elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\ else echo "Packet manager not found"; echo "Internal error"; exit 1;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi fi;\
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
+5 -5
View File
@@ -1,8 +1,8 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\ if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\ elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\ elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\ elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\ elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
else pm="uname"; opt="-a";\ else pm="uname"; opt="-a";\
fi;\ fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\ CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
+28 -19
View File
@@ -1,25 +1,34 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\ elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\ elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\ fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\ if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\ if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\ if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
if ! command -v docker > /dev/null 2>&1; then \ if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
sleep 5; sudo systemctl enable --now docker; sleep 5;\ sudo -n $pm $check_pkgs;\
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo -n $pm $silent_inst $docker_pkg;\
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
else \
echo "Container runtime is not supported";\
exit 1;\
fi;\
fi;\ fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \ if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\ if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
fi;\
fi;\ fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \ if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\ sleep 5; sudo -n systemctl start docker; sleep 5;\
sleep 5; sudo systemctl start docker; sleep 5;\ if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
fi;\ fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\ sudo -n docker --version || docker --version;\
docker --version;\
uname -sr uname -sr
@@ -1,6 +1,6 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\ sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\ sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\ sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia sudo rm -frd /opt/amnezia
+1 -2
View File
@@ -1,4 +1,3 @@
sudo docker stop $CONTAINER_NAME;\ sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\ sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME;\ sudo docker rmi $CONTAINER_NAME;
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
@@ -418,7 +418,8 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
{ {
bool isConnectEvent = newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig; bool isConnectEvent = newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig;
bool wasSubscriptionExpired = false; bool wasSubscriptionExpired = false;
if (const auto oldApiV2 = m_serversController->apiV2Config(serverId)) { const auto oldApiV2 = m_serversController->apiV2Config(serverId);
if (oldApiV2) {
wasSubscriptionExpired = oldApiV2->apiConfig.subscriptionExpiredByServer wasSubscriptionExpired = oldApiV2->apiConfig.subscriptionExpiredByServer
|| oldApiV2->apiConfig.isSubscriptionExpired(); || oldApiV2->apiConfig.isSubscriptionExpired();
} }
@@ -426,6 +427,10 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent); ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent);
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
if (!newCountryCode.isEmpty() && oldApiV2) {
m_previousCountryServerId = serverId;
m_previousApiV2Config = oldApiV2;
}
if (wasSubscriptionExpired) { if (wasSubscriptionExpired) {
emit subscriptionRefreshNeeded(); emit subscriptionRefreshNeeded();
} }
@@ -447,6 +452,20 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
} }
} }
void SubscriptionUiController::revertLastCountryChange()
{
if (m_previousCountryServerId.isEmpty() || !m_previousApiV2Config) {
return;
}
const QString serverId = m_previousCountryServerId;
const ApiV2ServerConfig cfg = *m_previousApiV2Config;
m_previousCountryServerId.clear();
m_previousApiV2Config.reset();
m_subscriptionController->restoreApiV2Config(serverId, cfg);
m_apiCountryModel->updateModel(cfg.apiConfig.availableCountries,
cfg.apiConfig.serverCountryCode);
}
bool SubscriptionUiController::deactivateDevice(const QString &serverId) bool SubscriptionUiController::deactivateDevice(const QString &serverId)
{ {
@@ -475,8 +494,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
void SubscriptionUiController::validateConfig() void SubscriptionUiController::validateConfig()
{ {
const QString serverId = m_serversController->getDefaultServerId(); const QString serverId = m_serversController->getDefaultServerId();
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) { if (serverId.isEmpty()) {
emit unsupportedConnectDrawerRequested();
emit configValidated(false); emit configValidated(false);
return; return;
} }
@@ -50,6 +50,7 @@ public slots:
bool importTrialFromGateway(const QString &email); bool importTrialFromGateway(const QString &email);
bool updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, const QString &newCountryName, bool updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig = false); bool reloadServiceConfig = false);
void revertLastCountryChange();
bool deactivateDevice(const QString &serverId); bool deactivateDevice(const QString &serverId);
bool deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode); bool deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
@@ -125,6 +126,9 @@ private:
ApiDevicesModel* m_apiDevicesModel; ApiDevicesModel* m_apiDevicesModel;
SettingsController* m_settingsController; SettingsController* m_settingsController;
ConnectionController* m_connectionController; ConnectionController* m_connectionController;
QString m_previousCountryServerId;
std::optional<ApiV2ServerConfig> m_previousApiV2Config;
}; };
#endif // SUBSCRIPTIONUICONTROLLER_H #endif // SUBSCRIPTIONUICONTROLLER_H
@@ -8,6 +8,8 @@
#include "amneziaApplication.h" #include "amneziaApplication.h"
#include "core/controllers/serversController.h" #include "core/controllers/serversController.h"
#include "core/models/containerConfig.h"
#include "core/utils/containerEnum.h"
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController, ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
ServersController* serversController, ServersController* serversController,
@@ -17,6 +19,7 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
m_serversController(serversController) m_serversController(serversController)
{ {
connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged); connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged);
connect(m_connectionController, &ConnectionController::serverSwitchFailed, this, &ConnectionUiController::serverSwitchFailed);
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection); connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
@@ -33,7 +36,7 @@ void ConnectionUiController::openConnection()
ErrorCode errorCode = m_connectionController->openConnection(serverId); ErrorCode errorCode = m_connectionController->openConnection(serverId);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit connectionErrorOccurred(errorCode); notifyConnectionBlocked(errorCode);
return; return;
} }
} }
@@ -63,6 +66,12 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
m_connectionStateText = tr("Connected"); m_connectionStateText = tr("Connected");
break; break;
} }
case Vpn::ConnectionState::Switching: {
m_isConnectionInProgress = true;
m_isConnected = true;
m_connectionStateText = tr("Switching...");
break;
}
case Vpn::ConnectionState::Connecting: { case Vpn::ConnectionState::Connecting: {
m_isConnectionInProgress = true; m_isConnectionInProgress = true;
break; break;
@@ -130,10 +139,36 @@ void ConnectionUiController::toggleConnection()
} else if (isConnected()) { } else if (isConnected()) {
closeConnection(); closeConnection();
} else { } else {
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
return;
}
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
if (errorCode != ErrorCode::NoError) {
notifyConnectionBlocked(errorCode);
return;
}
emit prepareConfig(); emit prepareConfig();
} }
} }
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
{
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
emit unsupportedConnectDrawerRequested();
return;
}
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
return;
}
emit connectionErrorOccurred(errorCode);
}
bool ConnectionUiController::isConnectionInProgress() const bool ConnectionUiController::isConnectionInProgress() const
{ {
return m_isConnectionInProgress; return m_isConnectionInProgress;
@@ -143,3 +178,32 @@ bool ConnectionUiController::isConnected() const
{ {
return m_isConnected; return m_isConnected;
} }
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
const QString &clientId) const
{
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
return false;
}
if (m_serversController->getDefaultServerId() != serverId) {
return false;
}
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
return false;
}
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return false;
}
const QString connectionClientId =
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
if (connectionClientId.isEmpty()) {
return false;
}
return connectionClientId == clientId || connectionClientId.contains(clientId);
}
@@ -35,6 +35,8 @@ public slots:
void openConnection(); void openConnection();
void closeConnection(); void closeConnection();
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
ErrorCode getLastConnectionError(); ErrorCode getLastConnectionError();
void onConnectionStateChanged(Vpn::ConnectionState state); void onConnectionStateChanged(Vpn::ConnectionState state);
@@ -48,9 +50,13 @@ signals:
void connectButtonClicked(); void connectButtonClicked();
void preparingConfig(); void preparingConfig();
void prepareConfig(); void prepareConfig();
void unsupportedConnectDrawerRequested();
void noInstalledContainers();
void serverSwitchFailed();
private: private:
Vpn::ConnectionState getCurrentConnectionState(); Vpn::ConnectionState getCurrentConnectionState();
void notifyConnectionBlocked(ErrorCode errorCode);
ConnectionController* m_connectionController; ConnectionController* m_connectionController;
ServersController* m_serversController; ServersController* m_serversController;
@@ -75,13 +75,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_connectionController(connectionController) m_connectionController(connectionController)
{ {
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated); connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) { connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
} else {
emit installationErrorOccurred(errorCode);
}
});
} }
InstallUiController::~InstallUiController() InstallUiController::~InstallUiController()
@@ -217,13 +211,11 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
emit installationErrorOccurred(errorCode); emit installationErrorOccurred(errorCode);
} }
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage) bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
{ {
DockerContainer container = static_cast<DockerContainer>(containerIndex); DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex); Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
containerConfig.container = container; containerConfig.container = container;
switch (protocolType) { switch (protocolType) {
@@ -271,6 +263,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
} }
#endif #endif
default: default:
return false;
}
return true;
}
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return;
}
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
return;
}
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return; return;
} }
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container); ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
@@ -305,13 +332,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
QFuture<ErrorCode> future = QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy, QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode { newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy); return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
}); });
watcher->setFuture(future); watcher->setFuture(future);
return; return;
} }
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig); ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container); ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
@@ -64,7 +64,8 @@ public slots:
void scanServerForInstalledContainers(const QString &serverId); void scanServerForInstalledContainers(const QString &serverId);
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true); void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void removeServer(const QString &serverId); void removeServer(const QString &serverId);
void rebootServer(const QString &serverId); void rebootServer(const QString &serverId);
@@ -132,7 +133,6 @@ signals:
void cachedProfileCleared(const QString &message); void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message); void apiConfigRemoved(const QString &message);
void noInstalledContainers();
void configValidated(bool isValid); void configValidated(bool isValid);
private: private:
@@ -162,6 +162,8 @@ private:
QString m_privateKeyPassphrase; QString m_privateKeyPassphrase;
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex); void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
}; };
#endif // INSTALLUICONTROLLER_H #endif // INSTALLUICONTROLLER_H
+16 -11
View File
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId); m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
if (!m_processedServerId.isEmpty()) {
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
} else {
updateContainersModel(); updateContainersModel();
}
}
updateDefaultServerContainersModel(); updateDefaultServerContainersModel();
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) { if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
m_processedServerId = normalizedServerId; m_processedServerId = normalizedServerId;
if (newIndex >= 0) { if (newIndex >= 0) {
updateContainersModel(); if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
for (const auto &description : m_orderedServerDescriptions) { if (description.isApiV2 && description.isCountrySelectionAvailable
if (description.serverId != normalizedServerId) { && !description.apiAvailableCountries.isEmpty()) {
continue;
}
if (description.isApiV2) {
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel(); emit updateApiCountryModel();
} }
emit updateApiServicesModel(); } else {
} updateContainersModel();
break;
} }
} }
@@ -113,7 +113,6 @@ signals:
void processedContainerIndexChanged(int index); void processedContainerIndexChanged(int index);
void hasServersFromGatewayApiChanged(); void hasServersFromGatewayApiChanged();
void updateApiCountryModel(); void updateApiCountryModel();
void updateApiServicesModel();
public: public:
void updateModel(); void updateModel();
@@ -22,12 +22,10 @@
SettingsUiController::SettingsUiController(SettingsController* settingsController, SettingsUiController::SettingsUiController(SettingsController* settingsController,
ServersController* serversController, ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent) QObject *parent)
: QObject(parent), : QObject(parent),
m_settingsController(settingsController), m_settingsController(settingsController),
m_serversController(serversController), m_serversController(serversController)
m_languageUiController(languageUiController)
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged); connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
{ {
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data); ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
emit appLanguageChanged( emit appLanguageChanged();
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled(); bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
emit amneziaDnsToggled(amneziaDnsEnabled); emit amneziaDnsToggled(amneziaDnsEnabled);
emit restoreBackupFinished(); emit restoreBackupFinished();
emit autoStartChanged();
emit startMinimizedChanged(); emit startMinimizedChanged();
} else { } else {
emit errorOccurred(errorCode); emit errorOccurred(errorCode);
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
void SettingsUiController::clearSettings() void SettingsUiController::clearSettings()
{ {
m_settingsController->clearSettings(); m_settingsController->clearSettings();
emit autoStartChanged();
emit startMinimizedChanged(); emit startMinimizedChanged();
emit resetLanguageToSystem(); emit resetLanguageToSystem();
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
void SettingsUiController::toggleAutoStart(bool enable) void SettingsUiController::toggleAutoStart(bool enable)
{ {
m_settingsController->toggleAutoStart(enable); m_settingsController->toggleAutoStart(enable);
if (!enable) { emit autoStartChanged();
emit startMinimizedChanged(); emit startMinimizedChanged();
}
} }
bool SettingsUiController::isStartMinimizedEnabled() bool SettingsUiController::isStartMinimizedEnabled()
+3 -5
View File
@@ -5,8 +5,6 @@
#include "core/controllers/settingsController.h" #include "core/controllers/settingsController.h"
#include "core/controllers/serversController.h" #include "core/controllers/serversController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/models/languageModel.h"
#include "core/utils/errorCodes.h" #include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h" #include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h" #include "core/utils/commonStructs.h"
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
public: public:
explicit SettingsUiController(SettingsController* settingsController, explicit SettingsUiController(SettingsController* settingsController,
ServersController* serversController, ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent = nullptr); QObject *parent = nullptr);
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
@@ -32,6 +29,7 @@ public:
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged) Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged) Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged) Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
public slots: public slots:
@@ -122,7 +120,7 @@ signals:
void loggingDisableByWatcher(); void loggingDisableByWatcher();
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language); void appLanguageChanged();
void resetLanguageToSystem(); void resetLanguageToSystem();
void onNotificationStateChanged(); void onNotificationStateChanged();
@@ -135,12 +133,12 @@ signals:
void activityResumed(); void activityResumed();
void isHomeAdLabelVisibleChanged(bool visible); void isHomeAdLabelVisibleChanged(bool visible);
void autoStartChanged();
void startMinimizedChanged(); void startMinimizedChanged();
private: private:
SettingsController* m_settingsController; SettingsController* m_settingsController;
ServersController* m_serversController; ServersController* m_serversController;
LanguageUiController* m_languageUiController;
}; };
#endif #endif
+1 -1
View File
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
switch (role) { switch (role) {
case SubscriptionStatusRole: { case SubscriptionStatusRole: {
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) { if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
return tr("Active"); return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
} }
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
auto userData = client.value(configKey::userData).toObject(); auto userData = client.value(configKey::userData).toObject();
switch (role) { switch (role) {
case ClientIdRole: return client.value(configKey::clientId).toString();
case ClientNameRole: return userData.value(configKey::clientName).toString(); case ClientNameRole: return userData.value(configKey::clientName).toString();
case CreationDateRole: return userData.value(configKey::creationDate).toString(); case CreationDateRole: return userData.value(configKey::creationDate).toString();
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString(); case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
QHash<int, QByteArray> ClientManagementModel::roleNames() const QHash<int, QByteArray> ClientManagementModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[ClientIdRole] = "clientId";
roles[ClientNameRole] = "clientName"; roles[ClientNameRole] = "clientName";
roles[CreationDateRole] = "creationDate"; roles[CreationDateRole] = "creationDate";
roles[LatestHandshakeRole] = "latestHandshake"; roles[LatestHandshakeRole] = "latestHandshake";
+2 -1
View File
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
ClientNameRole = Qt::UserRole + 1, ClientIdRole = Qt::UserRole + 1,
ClientNameRole,
CreationDateRole, CreationDateRole,
LatestHandshakeRole, LatestHandshakeRole,
DataReceivedRole, DataReceivedRole,
+4
View File
@@ -23,6 +23,10 @@ public:
Q_INVOKABLE int containerFromString(const QString &container) const { Q_INVOKABLE int containerFromString(const QString &container) const {
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container)); return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
} }
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
}
}; };
#endif // CONTAINERPROPS_H #endif // CONTAINERPROPS_H
+4 -1
View File
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex); case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container); case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
case IsShareableRole: return ContainerUtils::isShareable(container); case IsShareableRole: return ContainerUtils::isShareable(container);
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn; case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other; case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
case IsIpsecRole: return container == DockerContainer::Ipsec; case IsIpsecRole: return container == DockerContainer::Ipsec;
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
bool ContainersModel::isInstallationAllowed(DockerContainer container) bool ContainersModel::isInstallationAllowed(DockerContainer container)
{ {
return container != DockerContainer::Awg; return container != DockerContainer::Awg
&& !ContainerUtils::isUnsupportedContainer(container);
} }
void ContainersModel::openContainerSettings(int containerIndex) void ContainersModel::openContainerSettings(int containerIndex)
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[IsSupportedRole] = "isSupported"; roles[IsSupportedRole] = "isSupported";
roles[IsShareableRole] = "isShareable"; roles[IsShareableRole] = "isShareable";
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
roles[IsInstallationAllowedRole] = "isInstallationAllowed"; roles[IsInstallationAllowedRole] = "isInstallationAllowed";
roles[InstallPageOrderRole] = "installPageOrder"; roles[InstallPageOrderRole] = "installPageOrder";
+2
View File
@@ -39,6 +39,8 @@ public:
IsSupportedRole, IsSupportedRole,
IsShareableRole, IsShareableRole,
IsUnsupportedContainerRole,
InstallPageOrderRole, InstallPageOrderRole,
// Container type check roles // Container type check roles
@@ -56,14 +56,17 @@ ListViewType {
return return
} }
if (checked) { var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index)) if (!isInstalled) {
} else { ServersUiController.processedContainerIndex = containerIndex
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.closeTriggered() containersDropDown.closeTriggered()
return
} }
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
} }
MouseArea { MouseArea {
+7 -4
View File
@@ -74,19 +74,22 @@ ListViewType {
: AmneziaStyle.color.mutedGray : AmneziaStyle.color.mutedGray
checked: index === root.selectedIndex checked: index === root.selectedIndex
checkable: !ConnectionController.isConnected checkable: !ConnectionController.isConnectionInProgress
ButtonGroup.group: serversRadioButtonGroup ButtonGroup.group: serversRadioButtonGroup
onClicked: { onClicked: {
if (ConnectionController.isConnected) { if (ConnectionController.isConnectionInProgress) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) PageController.showNotificationMessage(qsTr("Unable to change server while connection is in progress"))
return return
} }
root.selectedIndex = index root.selectedIndex = index
ServersUiController.setDefaultServerAtIndex(index) ServersUiController.setDefaultServerAtIndex(index)
if (ConnectionController.isConnected) {
ConnectionController.openConnection()
}
} }
Keys.onEnterPressed: serverRadioButton.clicked() Keys.onEnterPressed: serverRadioButton.clicked()
@@ -5,7 +5,6 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import PageEnum 1.0 import PageEnum 1.0
import ContainerProps 1.0
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
+30 -9
View File
@@ -6,8 +6,36 @@ Menu {
popupType: Popup.Native popupType: Popup.Native
onAboutToShow: blocker.enabled = true property Item inputBlocker: null
onClosed: blocker.enabled = false
Component {
id: inputBlockerComponent
MouseArea {
anchors.fill: parent
preventStealing: true
}
}
onAboutToShow: {
if (!textObj || !textObj.window) {
return
}
const contentItem = textObj.window.contentItem
if (!inputBlocker) {
inputBlocker = inputBlockerComponent.createObject(contentItem)
} else {
inputBlocker.parent = contentItem
}
}
onClosed: {
if (inputBlocker) {
inputBlocker.destroy()
inputBlocker = null
}
}
MenuItem { MenuItem {
text: qsTr("C&ut") text: qsTr("C&ut")
@@ -31,11 +59,4 @@ Menu {
enabled: textObj.length > 0 enabled: textObj.length > 0
onTriggered: textObj.selectAll() onTriggered: textObj.selectAll()
} }
MouseArea {
id: blocker
z: 2
enabled: false
preventStealing: true
}
} }
+2 -2
View File
@@ -25,8 +25,8 @@ PageType {
filters: [ filters: [
ValueFilter { ValueFilter {
roleName: "isCurrentlyProcessed" roleName: "serverId"
value: true value: ServersUiController.processedServerId
} }
] ]
} }

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