Merge branch 'dev' into feature/killswitch-strict-mode
This commit is contained in:
commit
c186be1788
115 changed files with 7477 additions and 5998 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,6 +1,3 @@
|
||||||
[submodule "client/3rd/OpenVPNAdapter"]
|
|
||||||
path = client/3rd/OpenVPNAdapter
|
|
||||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
|
||||||
[submodule "client/3rd/qtkeychain"]
|
[submodule "client/3rd/qtkeychain"]
|
||||||
path = client/3rd/qtkeychain
|
path = client/3rd/qtkeychain
|
||||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.3.0
|
project(${PROJECT} VERSION 4.8.3.3
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,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 2072)
|
set(APP_ANDROID_VERSION_CODE 2076)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ GPL v3.0
|
||||||
|
|
||||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ GPL v3.0
|
||||||
|
|
||||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||||
|
|
||||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61
|
Subproject commit b714700addf0c6e773367b13f2211f63110171a1
|
||||||
1
client/3rd/OpenVPNAdapter
vendored
1
client/3rd/OpenVPNAdapter
vendored
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b
|
|
||||||
|
|
@ -96,11 +96,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
|
||||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||||
# -- i18n end
|
# -- i18n end
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(IS_CI ${CI})
|
set(IS_CI ${CI})
|
||||||
if(IS_CI)
|
if(IS_CI)
|
||||||
message("Detected CI env")
|
message("Detected CI env")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include <QLocalSocket>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
|
|
@ -10,8 +12,6 @@
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QLocalServer>
|
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/models/installedAppsModel.h"
|
#include "ui/models/installedAppsModel.h"
|
||||||
|
|
@ -282,16 +282,17 @@ bool AmneziaApplication::parseCommands()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
void AmneziaApplication::startLocalServer() {
|
void AmneziaApplication::startLocalServer()
|
||||||
|
{
|
||||||
const QString serverName("AmneziaVPNInstance");
|
const QString serverName("AmneziaVPNInstance");
|
||||||
QLocalServer::removeServer(serverName);
|
QLocalServer::removeServer(serverName);
|
||||||
|
|
||||||
QLocalServer* server = new QLocalServer(this);
|
QLocalServer *server = new QLocalServer(this);
|
||||||
server->listen(serverName);
|
server->listen(serverName);
|
||||||
|
|
||||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||||
if (server) {
|
if (server) {
|
||||||
QLocalSocket* clientConnection = server->nextPendingConnection();
|
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||||
clientConnection->deleteLater();
|
clientConnection->deleteLater();
|
||||||
}
|
}
|
||||||
emit m_pageController->raiseMainWindow();
|
emit m_pageController->raiseMainWindow();
|
||||||
|
|
@ -418,7 +419,9 @@ void AmneziaApplication::initControllers()
|
||||||
&ConnectionController::onCurrentContainerUpdated);
|
&ConnectionController::onCurrentContainerUpdated);
|
||||||
|
|
||||||
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||||
|
if (m_reloadConfigErrorOccurredConnection) {
|
||||||
disconnect(m_reloadConfigErrorOccurredConnection);
|
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||||
|
}
|
||||||
emit m_connectionController->configFromApiUpdated();
|
emit m_connectionController->configFromApiUpdated();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -426,7 +429,7 @@ void AmneziaApplication::initControllers()
|
||||||
m_reloadConfigErrorOccurredConnection = connect(
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::SingleShotConnection));
|
||||||
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -434,7 +437,7 @@ void AmneziaApplication::initControllers()
|
||||||
m_reloadConfigErrorOccurredConnection = connect(
|
m_reloadConfigErrorOccurredConnection = connect(
|
||||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
static_cast<Qt::ConnectionType>(Qt::AutoConnection | Qt::SingleShotConnection));
|
||||||
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||||
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
<!-- for TV -->
|
<!-- for TV -->
|
||||||
<uses-feature android:name="android.software.leanback" android:required="true" />
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
|
|
||||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
<!-- The following comment will be replaced upon deployment with default features based on the dependencies
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,7 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
|
|
||||||
)
|
)
|
||||||
set_target_properties(${PROJECT} PROPERTIES
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
|
|
@ -126,9 +122,9 @@ add_subdirectory(ios/networkextension)
|
||||||
add_dependencies(${PROJECT} networkextension)
|
add_dependencies(${PROJECT} networkextension)
|
||||||
|
|
||||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
{
|
{
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||||
if (l.isEmpty()) {
|
if (l.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -110,22 +110,19 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||||
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,
|
{ DockerContainer::ShadowSocks,
|
||||||
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
|
QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
|
||||||
"may be recognized by analysis systems in some highly censored regions.") },
|
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||||
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
|
"active-probing detection. It is very resistant to detection, but offers low speed.") },
|
||||||
"of censorship.") },
|
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("WireGuard - New 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. Recommended for regions with low levels of censorship.") },
|
"consumption.") },
|
||||||
{ DockerContainer::Awg,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||||
"but very resistant to blockages. "
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||||
"Recommended for regions with high levels of censorship.") },
|
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
||||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
"It is highly resistant to detection and offers high speed.") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
||||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||||
|
|
@ -156,7 +153,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* Normal power consumption on mobile devices\n"
|
"* Normal power consumption on mobile devices\n"
|
||||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||||
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
|
"* Recognised by DPI systems and therefore susceptible to blocking\n"
|
||||||
"* Can operate over both TCP and UDP network protocols.") },
|
"* Can operate over both TCP and UDP network protocols.") },
|
||||||
{ DockerContainer::ShadowSocks,
|
{ DockerContainer::ShadowSocks,
|
||||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||||
|
|
@ -169,28 +166,26 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||||
"* Works over TCP network protocol.") },
|
"* Works over TCP network protocol.") },
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||||
"protecting against blocking.\n\n"
|
"protecting against detection.\n\n"
|
||||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||||
"and the server.\n\n"
|
"and the server.\n\n"
|
||||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
"Cloak protects OpenVPN from detection. \n\n"
|
||||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||||
"being detected\n\n"
|
"being detected\n\n"
|
||||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||||
"invisible to analysis systems.\n\n"
|
"invisible to analysis systems.\n\n"
|
||||||
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
|
|
||||||
"OpenVPN over Cloak from the first connection\n\n"
|
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* High power consumption on mobile devices\n"
|
"* High power consumption on mobile devices\n"
|
||||||
"* Flexible settings\n"
|
"* Flexible settings\n"
|
||||||
"* Not recognised by DPI analysis systems\n"
|
"* Not recognised by detection systems\n"
|
||||||
"* Works over TCP network protocol, 443 port.\n") },
|
"* Works over TCP network protocol, 443 port.\n") },
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
|
||||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||||
|
|
@ -213,18 +208,18 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Available in the AmneziaVPN across all platforms\n"
|
||||||
"* Low power consumption\n"
|
"* Low power consumption\n"
|
||||||
"* Minimum number of settings\n"
|
"* Minimum number of settings\n"
|
||||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
"* Not recognised by traffic analysis systems\n"
|
||||||
"* Works over UDP network protocol.") },
|
"* Works over UDP network protocol.") },
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
|
||||||
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
|
||||||
"thus presenting an authentic TLS certificate and data. \n"
|
"thus presenting an authentic TLS certificate and data. \n"
|
||||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||||
"legitimate sites without the need for specific configurations. \n"
|
"legitimate sites without the need for specific configurations. \n"
|
||||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
|
||||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
"This makes REALITY a robust solution for maintaining internet freedom.")
|
||||||
},
|
},
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||||
|
|
@ -332,9 +327,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
||||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return true;
|
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
// case DockerContainer::Cloak: return true;
|
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -342,9 +335,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("Low");
|
case DockerContainer::Awg: return tr("Automatic");
|
||||||
case DockerContainer::Awg: return tr("High");
|
|
||||||
// case DockerContainer::Cloak: return tr("Extreme");
|
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,10 +343,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
|
||||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||||
// case DockerContainer::Cloak:
|
|
||||||
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -363,9 +352,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return 3;
|
case DockerContainer::Awg: return 1;
|
||||||
case DockerContainer::Awg: return 2;
|
|
||||||
// case DockerContainer::Cloak: return 1;
|
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,8 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::SslHandshakeFailedError) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
||||||
} else {
|
} else {
|
||||||
QString err = reply->errorString();
|
QString err = reply->errorString();
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
|
@ -323,10 +325,8 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||||
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
|
||||||
qDebug().noquote() << errors;
|
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) { qDebug().noquote() << errors; });
|
||||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,9 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (container == DockerContainer::Awg) {
|
||||||
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||||
|
|
@ -370,8 +372,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::WireGuard) {
|
if (container == DockerContainer::WireGuard) {
|
||||||
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||||
|
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -607,6 +611,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||||
|
|
||||||
// Amnezia wireguard vars
|
// Amnezia wireguard vars
|
||||||
|
vars.append({ { "$AWG_SUBNET_IP",
|
||||||
|
amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||||
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||||
|
|
||||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||||
|
|
|
||||||
|
|
@ -114,12 +114,23 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
|
|
||||||
// Bring up the wireguard interface if not already done.
|
// Bring up the wireguard interface if not already done.
|
||||||
if (!wgutils()->interfaceExists()) {
|
if (!wgutils()->interfaceExists()) {
|
||||||
|
// Create the interface.
|
||||||
if (!wgutils()->addInterface(config)) {
|
if (!wgutils()->addInterface(config)) {
|
||||||
logger.error() << "Interface creation failed.";
|
logger.error() << "Interface creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bring the interface up.
|
||||||
|
if (supportIPUtils()) {
|
||||||
|
if (!iputils()->addInterfaceIPs(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!iputils()->setMTUAndUp(config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure routing for excluded addresses.
|
// Configure routing for excluded addresses.
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
addExclusionRoute(IPAddress(i));
|
addExclusionRoute(IPAddress(i));
|
||||||
|
|
@ -135,15 +146,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportIPUtils()) {
|
|
||||||
if (!iputils()->addInterfaceIPs(config)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!iputils()->setMTUAndUp(config)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set routing
|
// set routing
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "daemon/daemonerrors.h"
|
||||||
|
#include "daemonerrors.h"
|
||||||
#include "dnsutils.h"
|
#include "dnsutils.h"
|
||||||
#include "interfaceconfig.h"
|
#include "interfaceconfig.h"
|
||||||
#include "iputils.h"
|
#include "iputils.h"
|
||||||
|
|
@ -51,7 +53,7 @@ class Daemon : public QObject {
|
||||||
*/
|
*/
|
||||||
void activationFailure();
|
void activationFailure();
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure();
|
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||||
|
|
|
||||||
17
client/daemon/daemonerrors.h
Normal file
17
client/daemon/daemonerrors.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
enum class DaemonError : uint8_t {
|
||||||
|
ERROR_NONE = 0u,
|
||||||
|
ERROR_FATAL = 1u,
|
||||||
|
ERROR_SPLIT_TUNNEL_INIT_FAILURE = 2u,
|
||||||
|
ERROR_SPLIT_TUNNEL_START_FAILURE = 3u,
|
||||||
|
ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE = 4u,
|
||||||
|
|
||||||
|
DAEMON_ERROR_MAX = 5u,
|
||||||
|
};
|
||||||
|
|
@ -159,9 +159,10 @@ void DaemonLocalServerConnection::disconnected() {
|
||||||
write(obj);
|
write(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DaemonLocalServerConnection::backendFailure() {
|
void DaemonLocalServerConnection::backendFailure(DaemonError err) {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj.insert("type", "backendFailure");
|
obj.insert("type", "backendFailure");
|
||||||
|
obj.insert("errorCode", static_cast<int>(err));
|
||||||
write(obj);
|
write(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "daemonerrors.h"
|
||||||
|
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
|
|
||||||
class DaemonLocalServerConnection final : public QObject {
|
class DaemonLocalServerConnection final : public QObject {
|
||||||
|
|
@ -23,7 +25,7 @@ class DaemonLocalServerConnection final : public QObject {
|
||||||
|
|
||||||
void connected(const QString& pubkey);
|
void connected(const QString& pubkey);
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure();
|
void backendFailure(DaemonError err);
|
||||||
|
|
||||||
void write(const QJsonObject& obj);
|
void write(const QJsonObject& obj);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ class WireguardUtils : public QObject {
|
||||||
|
|
||||||
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||||
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||||
|
|
||||||
|
virtual bool excludeLocalNetworks(const QList<IPAddress>& addresses) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIREGUARDUTILS_H
|
#endif // WIREGUARDUTILS_H
|
||||||
|
|
|
||||||
5
client/images/controls/external-link.svg
Normal file
5
client/images/controls/external-link.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18 13V19C18 19.5304 17.7893 20.0391 17.4142 20.4142C17.0391 20.7893 16.5304 21 16 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V8C3 7.46957 3.21071 6.96086 3.58579 6.58579C3.96086 6.21071 4.46957 6 5 6H11" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M15 3H21V9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 14L21 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 644 B |
|
|
@ -27,12 +27,7 @@ set_target_properties(networkextension PROPERTIES
|
||||||
|
|
||||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||||
|
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
|
||||||
|
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension"
|
|
||||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(networkextension PROPERTIES
|
set_target_properties(networkextension PROPERTIES
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
XCODEBUILD="/usr/bin/xcodebuild"
|
|
||||||
WORKINGDIR=`pwd`
|
|
||||||
PATCH="/usr/bin/patch"
|
|
||||||
|
|
||||||
cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
|
|
||||||
cat << EOF >> $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
|
|
||||||
PROJECT_TEMP_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/OpenVPNAdapter.build
|
|
||||||
CONFIGURATION_BUILD_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos
|
|
||||||
BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos
|
|
||||||
EOF
|
|
||||||
|
|
||||||
|
|
||||||
cd 3rd/OpenVPNAdapter
|
|
||||||
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk iphoneos -destination 'generic/platform=iOS' -project OpenVPNAdapter.xcodeproj ; then
|
|
||||||
echo "OpenVPNAdapter built successfully"
|
|
||||||
else
|
|
||||||
echo "OpenVPNAdapter build failed"
|
|
||||||
fi
|
|
||||||
cd ../../
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include "protocols/protocols_defs.h"
|
|
||||||
#include "localsocketcontroller.h"
|
#include "localsocketcontroller.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
|
@ -17,6 +18,9 @@
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "models/server.h"
|
#include "models/server.h"
|
||||||
|
#include "daemon/daemonerrors.h"
|
||||||
|
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
// How many times do we try to reconnect.
|
// How many times do we try to reconnect.
|
||||||
constexpr int MAX_CONNECTION_RETRY = 10;
|
constexpr int MAX_CONNECTION_RETRY = 10;
|
||||||
|
|
@ -451,9 +455,40 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "backendFailure") {
|
if (type == "backendFailure") {
|
||||||
qCritical() << "backendFailure";
|
if (!obj.contains("errorCode")) {
|
||||||
|
// report a generic error if we dont know what it is.
|
||||||
|
logger.error() << "generic backend failure error";
|
||||||
|
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto errorCode = static_cast<uint8_t>(obj["errorCode"].toInt());
|
||||||
|
if (errorCode >= (uint8_t)DaemonError::DAEMON_ERROR_MAX) {
|
||||||
|
// Also report a generic error if the code is invalid.
|
||||||
|
logger.error() << "invalid backend failure error code";
|
||||||
|
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (static_cast<DaemonError>(errorCode)) {
|
||||||
|
case DaemonError::ERROR_NONE:
|
||||||
|
[[fallthrough]];
|
||||||
|
case DaemonError::ERROR_FATAL:
|
||||||
|
logger.error() << "generic backend failure error (fatal or error none)";
|
||||||
|
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||||
|
break;
|
||||||
|
case DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE:
|
||||||
|
[[fallthrough]];
|
||||||
|
case DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE:
|
||||||
|
[[fallthrough]];
|
||||||
|
case DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE:
|
||||||
|
logger.error() << "split tunnel backend failure error";
|
||||||
|
//REPORTERROR(ErrorHandler::SplitTunnelError, "controller");
|
||||||
|
break;
|
||||||
|
case DaemonError::DAEMON_ERROR_MAX:
|
||||||
|
// We should not get here.
|
||||||
|
Q_ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == "logs") {
|
if (type == "logs") {
|
||||||
// We don't care if we are not waiting for logs.
|
// We don't care if we are not waiting for logs.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import HevSocks5Tunnel
|
import HevSocks5Tunnel
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
public enum Socks5Tunnel {
|
public enum Socks5Tunnel {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,31 +299,6 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
|
||||||
return peerList;
|
return peerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
|
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -379,6 +354,26 @@ bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
return m_rtmonitor->deleteExclusionRoute(prefix);
|
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsLinux::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly discard LAN traffic that makes its way into the tunnel. This
|
||||||
|
// doesn't really exclude the LAN traffic, we just don't take any action to
|
||||||
|
// overrule the routes of other interfaces.
|
||||||
|
bool result = true;
|
||||||
|
for (const auto& prefix : routes) {
|
||||||
|
logger.error() << "Attempting to exclude:" << prefix.toString();
|
||||||
|
if (!m_rtmonitor->insertRoute(prefix)) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: A kill switch would be nice though :)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
QTimer uapiTimeout;
|
QTimer uapiTimeout;
|
||||||
|
|
@ -452,3 +447,27 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ public:
|
||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
void applyFirewallRules(FirewallParams& params);
|
void applyFirewallRules(FirewallParams& params);
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
|
||||||
|
|
@ -358,8 +358,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||||
unsigned int ifindex,
|
unsigned int ifindex, const void* gateway,
|
||||||
const void* gateway) {
|
int flags) {
|
||||||
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
||||||
sizeof(struct sockaddr_in6) * 2 +
|
sizeof(struct sockaddr_in6) * 2 +
|
||||||
sizeof(struct sockaddr_storage);
|
sizeof(struct sockaddr_storage);
|
||||||
|
|
@ -370,7 +370,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||||
rtm->rtm_version = RTM_VERSION;
|
rtm->rtm_version = RTM_VERSION;
|
||||||
rtm->rtm_type = action;
|
rtm->rtm_type = action;
|
||||||
rtm->rtm_index = ifindex;
|
rtm->rtm_index = ifindex;
|
||||||
rtm->rtm_flags = RTF_STATIC | RTF_UP;
|
rtm->rtm_flags = flags | RTF_STATIC | RTF_UP;
|
||||||
rtm->rtm_addrs = 0;
|
rtm->rtm_addrs = 0;
|
||||||
rtm->rtm_pid = 0;
|
rtm->rtm_pid = 0;
|
||||||
rtm->rtm_seq = m_rtseq++;
|
rtm->rtm_seq = m_rtseq++;
|
||||||
|
|
@ -490,7 +490,7 @@ bool MacosRouteMonitor::rtmFetchRoutes(int family) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) {
|
||||||
struct sockaddr_dl datalink;
|
struct sockaddr_dl datalink;
|
||||||
memset(&datalink, 0, sizeof(datalink));
|
memset(&datalink, 0, sizeof(datalink));
|
||||||
datalink.sdl_family = AF_LINK;
|
datalink.sdl_family = AF_LINK;
|
||||||
|
|
@ -502,11 +502,11 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
||||||
datalink.sdl_slen = 0;
|
datalink.sdl_slen = 0;
|
||||||
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
||||||
|
|
||||||
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
|
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) {
|
||||||
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
|
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ class MacosRouteMonitor final : public QObject {
|
||||||
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
||||||
~MacosRouteMonitor();
|
~MacosRouteMonitor();
|
||||||
|
|
||||||
bool insertRoute(const IPAddress& prefix);
|
bool insertRoute(const IPAddress& prefix, int flags = 0);
|
||||||
bool deleteRoute(const IPAddress& prefix);
|
bool deleteRoute(const IPAddress& prefix, int flags = 0);
|
||||||
int interfaceFlags() { return m_ifflags; }
|
int interfaceFlags() { return m_ifflags; }
|
||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix);
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
|
|
@ -37,7 +37,7 @@ class MacosRouteMonitor final : public QObject {
|
||||||
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||||
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
||||||
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
|
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
|
||||||
const void* gateway);
|
const void* gateway, int flags = 0);
|
||||||
bool rtmFetchRoutes(int family);
|
bool rtmFetchRoutes(int family);
|
||||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||||
const void* sa);
|
const void* sa);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "wireguardutilsmacos.h"
|
#include "wireguardutilsmacos.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <net/route.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
|
@ -132,7 +133,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 {
|
} else {
|
||||||
|
|
@ -213,7 +213,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||||
logger.warning() << "Failed to create peer with no endpoints";
|
logger.warning() << "Failed to create peer with no endpoints";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
out << config.m_serverPort << "\n";
|
out << config.m_serverPort << "\n";
|
||||||
|
|
||||||
out << "replace_allowed_ips=true\n";
|
out << "replace_allowed_ips=true\n";
|
||||||
|
|
@ -325,10 +324,10 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prefix.prefixLength() > 0) {
|
|
||||||
return m_rtmonitor->insertRoute(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (prefix.prefixLength() > 0) {
|
||||||
|
return m_rtmonitor->deleteRoute(prefix);
|
||||||
|
}
|
||||||
// Ensure that we do not replace the default route.
|
// Ensure that we do not replace the default route.
|
||||||
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
|
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
|
||||||
|
|
@ -348,31 +347,6 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
|
||||||
return m_rtmonitor->addExclusionRoute(prefix);
|
return m_rtmonitor->addExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
if (!m_rtmonitor) {
|
if (!m_rtmonitor) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -380,6 +354,26 @@ bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
return m_rtmonitor->deleteExclusionRoute(prefix);
|
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsMacos::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||||
|
if (!m_rtmonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly discard LAN traffic that makes its way into the tunnel. This
|
||||||
|
// doesn't really exclude the LAN traffic, we just don't take any action to
|
||||||
|
// overrule the routes of other interfaces.
|
||||||
|
bool result = true;
|
||||||
|
for (const auto& prefix : routes) {
|
||||||
|
logger.error() << "Attempting to exclude:" << prefix.toString();
|
||||||
|
if (!m_rtmonitor->insertRoute(prefix, RTF_IFSCOPE | RTF_REJECT)) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: A kill switch would be nice though :)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
QTimer uapiTimeout;
|
QTimer uapiTimeout;
|
||||||
|
|
@ -456,3 +450,28 @@ 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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
void applyFirewallRules(FirewallParams& params);
|
void applyFirewallRules(FirewallParams& params);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "windowsdaemon.h"
|
#include "windowsdaemon.h"
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#include <qassert.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
|
@ -15,28 +16,34 @@
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include "daemon/daemonerrors.h"
|
||||||
#include "dnsutilswindows.h"
|
#include "dnsutilswindows.h"
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "core/networkUtilities.h"
|
#include "platforms/windows/daemon/windowsfirewall.h"
|
||||||
|
#include "platforms/windows/daemon/windowssplittunnel.h"
|
||||||
#include "platforms/windows/windowscommons.h"
|
#include "platforms/windows/windowscommons.h"
|
||||||
#include "platforms/windows/windowsservicemanager.h"
|
|
||||||
#include "windowsfirewall.h"
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
|
#include "core/networkUtilities.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("WindowsDaemon");
|
Logger logger("WindowsDaemon");
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) {
|
WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
|
||||||
MZ_COUNT_CTOR(WindowsDaemon);
|
MZ_COUNT_CTOR(WindowsDaemon);
|
||||||
|
m_firewallManager = WindowsFirewall::create(this);
|
||||||
|
Q_ASSERT(m_firewallManager != nullptr);
|
||||||
|
|
||||||
m_wgutils = new WireguardUtilsWindows(this);
|
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
|
||||||
m_dnsutils = new DnsUtilsWindows(this);
|
m_dnsutils = new DnsUtilsWindows(this);
|
||||||
|
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
||||||
|
|
||||||
connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this,
|
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
|
||||||
&WindowsDaemon::monitorBackendFailure);
|
&WindowsDaemon::monitorBackendFailure);
|
||||||
connect(this, &WindowsDaemon::activationFailure,
|
connect(this, &WindowsDaemon::activationFailure,
|
||||||
[]() { WindowsFirewall::instance()->disableKillSwitch(); });
|
[this]() { m_firewallManager->disableKillSwitch(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsDaemon::~WindowsDaemon() {
|
WindowsDaemon::~WindowsDaemon() {
|
||||||
|
|
@ -57,28 +64,42 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
|
||||||
|
|
||||||
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
||||||
if (config.m_vpnDisabledApps.length() > 0) {
|
if (config.m_vpnDisabledApps.length() > 0) {
|
||||||
m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
|
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
|
||||||
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
|
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
|
||||||
} else {
|
} else {
|
||||||
m_splitTunnelManager.stop();
|
m_splitTunnelManager->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
|
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
|
||||||
if (op == Down) {
|
if (!m_splitTunnelManager) {
|
||||||
m_splitTunnelManager.stop();
|
if (config.m_vpnDisabledApps.length() > 0) {
|
||||||
|
// The Client has sent us a list of disabled apps, but we failed
|
||||||
|
// to init the the split tunnel driver.
|
||||||
|
// So let the client know this was not possible
|
||||||
|
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op == Up) {
|
if (op == Down) {
|
||||||
logger.debug() << "Tunnel UP, Starting SplitTunneling";
|
m_splitTunnelManager->stop();
|
||||||
if (!WindowsSplitTunnel::isInstalled()) {
|
return true;
|
||||||
logger.warning() << "Split Tunnel Driver not Installed yet, fixing this.";
|
|
||||||
WindowsSplitTunnel::installDriver();
|
|
||||||
}
|
}
|
||||||
|
if (config.m_vpnDisabledApps.length() > 0) {
|
||||||
|
if (!m_splitTunnelManager->start(m_inetAdapterIndex)) {
|
||||||
|
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
|
||||||
|
};
|
||||||
|
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
|
||||||
|
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
|
||||||
|
};
|
||||||
|
// Now the driver should be running (State == 4)
|
||||||
|
if (!m_splitTunnelManager->isRunning()) {
|
||||||
|
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
activateSplitTunnel(config);
|
}
|
||||||
|
m_splitTunnelManager->stop();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@
|
||||||
#ifndef WINDOWSDAEMON_H
|
#ifndef WINDOWSDAEMON_H
|
||||||
#define WINDOWSDAEMON_H
|
#define WINDOWSDAEMON_H
|
||||||
|
|
||||||
|
#include <qpointer.h>
|
||||||
|
|
||||||
#include "daemon/daemon.h"
|
#include "daemon/daemon.h"
|
||||||
#include "dnsutilswindows.h"
|
#include "dnsutilswindows.h"
|
||||||
|
#include "windowsfirewall.h"
|
||||||
#include "windowssplittunnel.h"
|
#include "windowssplittunnel.h"
|
||||||
#include "windowstunnelservice.h"
|
#include "windowstunnelservice.h"
|
||||||
#include "wireguardutilswindows.h"
|
#include "wireguardutilswindows.h"
|
||||||
|
|
@ -25,7 +28,7 @@ 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; }
|
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -39,9 +42,10 @@ class WindowsDaemon final : public Daemon {
|
||||||
|
|
||||||
int m_inetAdapterIndex = -1;
|
int m_inetAdapterIndex = -1;
|
||||||
|
|
||||||
WireguardUtilsWindows* m_wgutils = nullptr;
|
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
|
||||||
DnsUtilsWindows* m_dnsutils = nullptr;
|
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||||
WindowsSplitTunnel m_splitTunnelManager;
|
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
||||||
|
QPointer<WindowsFirewall> m_firewallManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WINDOWSDAEMON_H
|
#endif // WINDOWSDAEMON_H
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@
|
||||||
#include <guiddef.h>
|
#include <guiddef.h>
|
||||||
#include <initguid.h>
|
#include <initguid.h>
|
||||||
#include <netfw.h>
|
#include <netfw.h>
|
||||||
//#include <qaccessible.h>
|
#include <qaccessible.h>
|
||||||
#include <Ws2tcpip.h>
|
#include <qassert.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <Ws2tcpip.h>
|
||||||
|
#include "winsock.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
@ -27,7 +28,6 @@
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "platforms/windows/windowsutils.h"
|
#include "platforms/windows/windowsutils.h"
|
||||||
#include "winsock.h"
|
|
||||||
|
|
||||||
#include "killswitch.h"
|
#include "killswitch.h"
|
||||||
|
|
||||||
|
|
@ -51,18 +51,13 @@ constexpr uint8_t HIGH_WEIGHT = 13;
|
||||||
constexpr uint8_t MAX_WEIGHT = 15;
|
constexpr uint8_t MAX_WEIGHT = 15;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
WindowsFirewall* WindowsFirewall::instance() {
|
WindowsFirewall* WindowsFirewall::create(QObject* parent) {
|
||||||
if (s_instance == nullptr) {
|
if (s_instance != nullptr) {
|
||||||
s_instance = new WindowsFirewall(qApp);
|
// Only one instance of the firewall is allowed
|
||||||
}
|
// Q_ASSERT(false);
|
||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
HANDLE engineHandle = nullptr;
|
||||||
WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
|
|
||||||
MZ_COUNT_CTOR(WindowsFirewall);
|
|
||||||
Q_ASSERT(s_instance == nullptr);
|
|
||||||
|
|
||||||
HANDLE engineHandle = NULL;
|
|
||||||
DWORD result = ERROR_SUCCESS;
|
DWORD result = ERROR_SUCCESS;
|
||||||
// Use dynamic sessions for efficiency and safety:
|
// Use dynamic sessions for efficiency and safety:
|
||||||
// -> Filtering policy objects are deleted even when the application crashes/
|
// -> Filtering policy objects are deleted even when the application crashes/
|
||||||
|
|
@ -73,15 +68,24 @@ WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
|
||||||
|
|
||||||
logger.debug() << "Opening the filter engine.";
|
logger.debug() << "Opening the filter engine.";
|
||||||
|
|
||||||
result =
|
result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session,
|
||||||
FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
|
&engineHandle);
|
||||||
|
|
||||||
if (result != ERROR_SUCCESS) {
|
if (result != ERROR_SUCCESS) {
|
||||||
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
|
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
logger.debug() << "Filter engine opened successfully.";
|
logger.debug() << "Filter engine opened successfully.";
|
||||||
m_sessionHandle = engineHandle;
|
if (!initSublayer()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
s_instance = new WindowsFirewall(engineHandle, parent);
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsFirewall::WindowsFirewall(HANDLE session, QObject* parent)
|
||||||
|
: QObject(parent), m_sessionHandle(session) {
|
||||||
|
MZ_COUNT_CTOR(WindowsFirewall);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsFirewall::~WindowsFirewall() {
|
WindowsFirewall::~WindowsFirewall() {
|
||||||
|
|
@ -91,15 +95,8 @@ WindowsFirewall::~WindowsFirewall() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::init() {
|
// static
|
||||||
if (m_init) {
|
bool WindowsFirewall::initSublayer() {
|
||||||
logger.warning() << "Alread initialised FW_WFP layer";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (m_sessionHandle == INVALID_HANDLE_VALUE) {
|
|
||||||
logger.error() << "Cant Init Sublayer with invalid wfp handle";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If we were not able to aquire a handle, this will fail anyway.
|
// If we were not able to aquire a handle, this will fail anyway.
|
||||||
// We need to open up another handle because of wfp rules:
|
// We need to open up another handle because of wfp rules:
|
||||||
// If a wfp resource was created with SESSION_DYNAMIC,
|
// If a wfp resource was created with SESSION_DYNAMIC,
|
||||||
|
|
@ -159,11 +156,10 @@ bool WindowsFirewall::init() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "Initialised Sublayer";
|
logger.debug() << "Initialised Sublayer";
|
||||||
m_init = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||||
// 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) \
|
||||||
|
|
@ -215,6 +211,36 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
||||||
#undef FW_OK
|
#undef FW_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow unprotected traffic sent to the following local address ranges.
|
||||||
|
bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
||||||
|
// Start the firewall transaction
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
disableKillSwitch();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto cleanup = qScopeGuard([&] {
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle);
|
||||||
|
disableKillSwitch();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Blocking unprotected traffic
|
||||||
|
for (const IPAddress& prefix : ranges) {
|
||||||
|
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||||
// Start the firewall transaction
|
// Start the firewall transaction
|
||||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
|
@ -253,9 +279,9 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||||
|
|
||||||
if (!config.m_excludedAddresses.empty()) {
|
if (!config.m_excludedAddresses.empty()) {
|
||||||
for (const QString& i : config.m_excludedAddresses) {
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
logger.debug() << "range: " << i;
|
logger.debug() << "excludedAddresses range: " << i;
|
||||||
|
|
||||||
if (!allowTrafficToRange(i, HIGH_WEIGHT,
|
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -440,6 +466,56 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||||
|
const QString& title,
|
||||||
|
const QString& peer) {
|
||||||
|
GUID layerKeyOut;
|
||||||
|
GUID layerKeyIn;
|
||||||
|
if (addr.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
} else {
|
||||||
|
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match the IP address range.
|
||||||
|
FWPM_FILTER_CONDITION0 cond[1] = {};
|
||||||
|
FWP_RANGE0 ipRange;
|
||||||
|
QByteArray lowIpV6Buffer;
|
||||||
|
QByteArray highIpV6Buffer;
|
||||||
|
|
||||||
|
importAddress(addr.address(), ipRange.valueLow, &lowIpV6Buffer);
|
||||||
|
importAddress(addr.broadcastAddress(), ipRange.valueHigh, &highIpV6Buffer);
|
||||||
|
|
||||||
|
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||||
|
cond[0].matchType = FWP_MATCH_RANGE;
|
||||||
|
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
||||||
|
cond[0].conditionValue.rangeValue = &ipRange;
|
||||||
|
|
||||||
|
// Assemble the Filter base
|
||||||
|
FWPM_FILTER0 filter;
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.action.type = FWP_ACTION_PERMIT;
|
||||||
|
filter.weight.type = FWP_UINT8;
|
||||||
|
filter.weight.uint8 = weight;
|
||||||
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
filter.numFilterConditions = 1;
|
||||||
|
filter.filterCondition = cond;
|
||||||
|
|
||||||
|
// Send the filters down to the firewall.
|
||||||
|
QString description = "Permit traffic %1 " + addr.toString();
|
||||||
|
filter.layerKey = layerKeyOut;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("to"), peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
filter.layerKey = layerKeyIn;
|
||||||
|
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
const QString& peer) {
|
||||||
|
|
@ -503,57 +579,6 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
|
||||||
const QString& title,
|
|
||||||
const QString& peer) {
|
|
||||||
QString description("Allow traffic %1 %2 ");
|
|
||||||
|
|
||||||
auto lower = addr.address();
|
|
||||||
auto upper = addr.broadcastAddress();
|
|
||||||
|
|
||||||
const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
|
|
||||||
const GUID layerKeyOut =
|
|
||||||
isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
|
||||||
const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
|
||||||
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
|
||||||
|
|
||||||
// Assemble the Filter base
|
|
||||||
FWPM_FILTER0 filter;
|
|
||||||
memset(&filter, 0, sizeof(filter));
|
|
||||||
filter.action.type = FWP_ACTION_PERMIT;
|
|
||||||
filter.weight.type = FWP_UINT8;
|
|
||||||
filter.weight.uint8 = weight;
|
|
||||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
|
||||||
|
|
||||||
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
|
||||||
FWP_RANGE0 ipRange;
|
|
||||||
QByteArray lowIpV6Buffer;
|
|
||||||
QByteArray highIpV6Buffer;
|
|
||||||
|
|
||||||
importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
|
|
||||||
importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
|
|
||||||
|
|
||||||
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
|
||||||
cond[0].matchType = FWP_MATCH_RANGE;
|
|
||||||
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
|
||||||
cond[0].conditionValue.rangeValue = &ipRange;
|
|
||||||
|
|
||||||
filter.numFilterConditions = 1;
|
|
||||||
filter.filterCondition = cond;
|
|
||||||
|
|
||||||
filter.layerKey = layerKeyOut;
|
|
||||||
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
|
||||||
peer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
filter.layerKey = layerKeyIn;
|
|
||||||
if (!enableFilter(&filter, title,
|
|
||||||
description.arg("from").arg(addr.toString()), peer)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||||
// Allow outbound DHCPv4
|
// Allow outbound DHCPv4
|
||||||
{
|
{
|
||||||
|
|
@ -753,7 +778,7 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||||
filter.weight.uint8 = weight;
|
filter.weight.uint8 = weight;
|
||||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
|
|
||||||
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
FWPM_FILTER_CONDITION0 cond[1] = {};
|
||||||
FWP_RANGE0 ipRange;
|
FWP_RANGE0 ipRange;
|
||||||
QByteArray lowIpV6Buffer;
|
QByteArray lowIpV6Buffer;
|
||||||
QByteArray highIpV6Buffer;
|
QByteArray highIpV6Buffer;
|
||||||
|
|
|
||||||
|
|
@ -26,19 +26,28 @@ struct FWP_CONDITION_VALUE0_;
|
||||||
|
|
||||||
class WindowsFirewall final : public QObject {
|
class WindowsFirewall final : public QObject {
|
||||||
public:
|
public:
|
||||||
~WindowsFirewall();
|
/**
|
||||||
|
* @brief Opens the Windows Filtering Platform, initializes the session,
|
||||||
|
* sublayer. Returns a WindowsFirewall object if successful, otherwise
|
||||||
|
* nullptr. If there is already a WindowsFirewall object, it will be returned.
|
||||||
|
*
|
||||||
|
* @param parent - parent QObject
|
||||||
|
* @return WindowsFirewall* - nullptr if failed to open the Windows Filtering
|
||||||
|
* Platform.
|
||||||
|
*/
|
||||||
|
static WindowsFirewall* create(QObject* parent);
|
||||||
|
~WindowsFirewall() override;
|
||||||
|
|
||||||
static WindowsFirewall* instance();
|
bool enableInterface(int vpnAdapterIndex);
|
||||||
bool init();
|
bool enableLanBypass(const QList<IPAddress>& ranges);
|
||||||
|
|
||||||
bool enableKillSwitch(int vpnAdapterIndex);
|
|
||||||
bool enablePeerTraffic(const InterfaceConfig& config);
|
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||||
bool disablePeerTraffic(const QString& pubkey);
|
bool disablePeerTraffic(const QString& pubkey);
|
||||||
bool disableKillSwitch();
|
bool disableKillSwitch();
|
||||||
bool allowAllTraffic();
|
bool allowAllTraffic();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WindowsFirewall(QObject* parent);
|
static bool initSublayer();
|
||||||
|
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_activeRules;
|
||||||
|
|
@ -51,11 +60,10 @@ class WindowsFirewall final : public QObject {
|
||||||
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, const QString& peer = QString());
|
||||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||||
|
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
|
||||||
|
const QString& peer = QString());
|
||||||
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, const QString& peer = QString());
|
||||||
bool allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
|
||||||
const QString& title,
|
|
||||||
const QString& peer);
|
|
||||||
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
const QString& title);
|
const QString& title);
|
||||||
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ namespace {
|
||||||
Logger logger("WindowsRouteMonitor");
|
Logger logger("WindowsRouteMonitor");
|
||||||
}; // namespace
|
}; // namespace
|
||||||
|
|
||||||
|
// Attempt to mark routing entries that we create with a relatively
|
||||||
|
// high metric. This ensures that we can skip over routes of our own
|
||||||
|
// creation when processing route changes, and ensures that we give
|
||||||
|
// way to other routing entries.
|
||||||
|
constexpr const ULONG EXCLUSION_ROUTE_METRIC = 0x5e72;
|
||||||
|
|
||||||
// Called by the kernel on route changes - perform some basic filtering and
|
// Called by the kernel on route changes - perform some basic filtering and
|
||||||
// invoke the routeChanged slot to do the real work.
|
// invoke the routeChanged slot to do the real work.
|
||||||
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
||||||
|
|
@ -20,22 +26,17 @@ static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
||||||
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
|
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
|
|
||||||
// Ignore host route changes, and unsupported protocols.
|
// Ignore route changes that we created.
|
||||||
if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
|
||||||
if (row->DestinationPrefix.PrefixLength >= 128) {
|
(row->Metric == EXCLUSION_ROUTE_METRIC)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
|
if (monitor->getLuid() == row->InterfaceLuid.Value) {
|
||||||
if (row->DestinationPrefix.PrefixLength >= 32) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor->getLuid() != row->InterfaceLuid.Value) {
|
// Invoke the route changed signal to do the real work in Qt.
|
||||||
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform prefix matching comparison on IP addresses in host order.
|
// Perform prefix matching comparison on IP addresses in host order.
|
||||||
|
|
@ -57,7 +58,8 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
|
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||||
|
: QObject(parent), m_luid(luid) {
|
||||||
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||||
logger.debug() << "WindowsRouteMonitor created.";
|
logger.debug() << "WindowsRouteMonitor created.";
|
||||||
|
|
||||||
|
|
@ -67,11 +69,13 @@ WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
|
||||||
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||||
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||||
CancelMibChangeNotify2(m_routeHandle);
|
CancelMibChangeNotify2(m_routeHandle);
|
||||||
flushExclusionRoutes();
|
|
||||||
|
flushRouteTable(m_exclusionRoutes);
|
||||||
|
flushRouteTable(m_clonedRoutes);
|
||||||
logger.debug() << "WindowsRouteMonitor destroyed.";
|
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
|
||||||
PMIB_IPINTERFACE_TABLE table;
|
PMIB_IPINTERFACE_TABLE table;
|
||||||
DWORD result = GetIpInterfaceTable(family, &table);
|
DWORD result = GetIpInterfaceTable(family, &table);
|
||||||
if (result != NO_ERROR) {
|
if (result != NO_ERROR) {
|
||||||
|
|
@ -82,10 +86,10 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
||||||
|
|
||||||
// Flush the list of interfaces that are valid for routing.
|
// Flush the list of interfaces that are valid for routing.
|
||||||
if ((family == AF_INET) || (family == AF_UNSPEC)) {
|
if ((family == AF_INET) || (family == AF_UNSPEC)) {
|
||||||
m_validInterfacesIpv4.clear();
|
m_interfaceMetricsIpv4.clear();
|
||||||
}
|
}
|
||||||
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
|
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
|
||||||
m_validInterfacesIpv6.clear();
|
m_interfaceMetricsIpv6.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the list of interfaces that are valid for routing.
|
// Rebuild the list of interfaces that are valid for routing.
|
||||||
|
|
@ -101,12 +105,12 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
||||||
if (row->Family == AF_INET) {
|
if (row->Family == AF_INET) {
|
||||||
logger.debug() << "Interface" << row->InterfaceIndex
|
logger.debug() << "Interface" << row->InterfaceIndex
|
||||||
<< "is valid for IPv4 routing";
|
<< "is valid for IPv4 routing";
|
||||||
m_validInterfacesIpv4.append(row->InterfaceLuid.Value);
|
m_interfaceMetricsIpv4[row->InterfaceLuid.Value] = row->Metric;
|
||||||
}
|
}
|
||||||
if (row->Family == AF_INET6) {
|
if (row->Family == AF_INET6) {
|
||||||
logger.debug() << "Interface" << row->InterfaceIndex
|
logger.debug() << "Interface" << row->InterfaceIndex
|
||||||
<< "is valid for IPv6 routing";
|
<< "is valid for IPv6 routing";
|
||||||
m_validInterfacesIpv6.append(row->InterfaceLuid.Value);
|
m_interfaceMetricsIpv6[row->InterfaceLuid.Value] = row->Metric;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,72 +130,72 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
||||||
if (row->InterfaceLuid.Value == m_luid) {
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore host routes, and shorter potential matches.
|
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||||
if (row->DestinationPrefix.PrefixLength >=
|
|
||||||
data->DestinationPrefix.PrefixLength) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
// Ignore routes of our own creation.
|
||||||
|
if ((row->Protocol == data->Protocol) && (row->Metric == data->Metric)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the routing table entry matches the destination.
|
// Check if the routing table entry matches the destination.
|
||||||
|
if (!routeContainsDest(&row->DestinationPrefix, &data->DestinationPrefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the combined interface and routing metric.
|
||||||
|
ULONG routeMetric = row->Metric;
|
||||||
if (data->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
if (data->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||||
if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) {
|
if (!m_interfaceMetricsIpv6.contains(row->InterfaceLuid.Value)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!m_validInterfacesIpv6.contains(row->InterfaceLuid.Value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
|
||||||
&row->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
|
||||||
row->DestinationPrefix.PrefixLength) != 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
routeMetric += m_interfaceMetricsIpv6[row->InterfaceLuid.Value];
|
||||||
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
|
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||||
if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) {
|
if (!m_interfaceMetricsIpv4.contains(row->InterfaceLuid.Value)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!m_validInterfacesIpv4.contains(row->InterfaceLuid.Value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
|
||||||
&row->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
|
||||||
row->DestinationPrefix.PrefixLength) != 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
routeMetric += m_interfaceMetricsIpv4[row->InterfaceLuid.Value];
|
||||||
} else {
|
} else {
|
||||||
// Unsupported destination address family.
|
// Unsupported destination address family.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (routeMetric < row->Metric) {
|
||||||
|
routeMetric = ULONG_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer routes with lower metric if we find multiple matches
|
// Prefer routes with lower metric if we find multiple matches
|
||||||
// with the same prefix length.
|
// with the same prefix length.
|
||||||
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
|
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
|
||||||
(row->Metric >= bestMetric)) {
|
(routeMetric >= bestMetric)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here, then this is the longest prefix match so far.
|
// If we got here, then this is the longest prefix match so far.
|
||||||
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
|
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
|
||||||
bestLuid = row->InterfaceLuid.Value;
|
|
||||||
bestMatch = row->DestinationPrefix.PrefixLength;
|
bestMatch = row->DestinationPrefix.PrefixLength;
|
||||||
bestMetric = row->Metric;
|
bestMetric = routeMetric;
|
||||||
|
if (bestMatch == data->DestinationPrefix.PrefixLength) {
|
||||||
|
bestLuid = 0; // Don't write to the table if we find an exact match.
|
||||||
|
} else {
|
||||||
|
bestLuid = row->InterfaceLuid.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If neither the interface nor next-hop have changed, then do nothing.
|
// If neither the interface nor next-hop have changed, then do nothing.
|
||||||
if ((data->InterfaceLuid.Value) == bestLuid &&
|
if (data->InterfaceLuid.Value == bestLuid &&
|
||||||
memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) {
|
memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the routing table entry.
|
// Delete the previous routing table entry, if any.
|
||||||
if (data->InterfaceLuid.Value != 0) {
|
if (data->InterfaceLuid.Value != 0) {
|
||||||
DWORD result = DeleteIpForwardEntry2(data);
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
||||||
logger.error() << "Failed to delete route:" << result;
|
logger.error() << "Failed to delete route:" << result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the routing table entry.
|
||||||
data->InterfaceLuid.Value = bestLuid;
|
data->InterfaceLuid.Value = bestLuid;
|
||||||
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
|
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
|
||||||
if (data->InterfaceLuid.Value != 0) {
|
if (data->InterfaceLuid.Value != 0) {
|
||||||
|
|
@ -202,10 +206,178 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool WindowsRouteMonitor::routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||||
|
const IP_ADDRESS_PREFIX* dest) {
|
||||||
|
if (route->Prefix.si_family != dest->Prefix.si_family) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (route->PrefixLength > dest->PrefixLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (route->Prefix.si_family == AF_INET) {
|
||||||
|
return prefixcmp(&route->Prefix.Ipv4.sin_addr, &dest->Prefix.Ipv4.sin_addr,
|
||||||
|
route->PrefixLength) == 0;
|
||||||
|
} else if (route->Prefix.si_family == AF_INET6) {
|
||||||
|
return prefixcmp(&route->Prefix.Ipv6.sin6_addr,
|
||||||
|
&dest->Prefix.Ipv6.sin6_addr, route->PrefixLength) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
QHostAddress WindowsRouteMonitor::prefixToAddress(
|
||||||
|
const IP_ADDRESS_PREFIX* dest) {
|
||||||
|
if (dest->Prefix.si_family == AF_INET6) {
|
||||||
|
return QHostAddress(dest->Prefix.Ipv6.sin6_addr.s6_addr);
|
||||||
|
} else if (dest->Prefix.si_family == AF_INET) {
|
||||||
|
quint32 addr = htonl(dest->Prefix.Ipv4.sin_addr.s_addr);
|
||||||
|
return QHostAddress(addr);
|
||||||
|
} else {
|
||||||
|
return QHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const {
|
||||||
|
auto i = m_exclusionRoutes.constBegin();
|
||||||
|
while (i != m_exclusionRoutes.constEnd()) {
|
||||||
|
const MIB_IPFORWARD_ROW2* row = i.value();
|
||||||
|
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::updateCapturedRoutes(int family) {
|
||||||
|
if (!m_defaultRouteCapture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PMIB_IPFORWARD_TABLE2 table;
|
||||||
|
DWORD error = GetIpForwardTable2(family, &table);
|
||||||
|
if (error != NO_ERROR) {
|
||||||
|
updateCapturedRoutes(family, table);
|
||||||
|
FreeMibTable(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
||||||
|
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||||
|
if (!m_defaultRouteCapture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
|
// Ignore routes into the VPN interface.
|
||||||
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ignore the default route
|
||||||
|
if (row->DestinationPrefix.PrefixLength == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ignore routes of our own creation.
|
||||||
|
if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
|
||||||
|
(row->Metric == EXCLUSION_ROUTE_METRIC)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ignore routes which should be excluded.
|
||||||
|
if (isRouteExcluded(&row->DestinationPrefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
||||||
|
if (destination.isLoopback() || destination.isBroadcast() ||
|
||||||
|
destination.isLinkLocal() || destination.isMulticast()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, this route should be cloned.
|
||||||
|
IPAddress prefix(destination, row->DestinationPrefix.PrefixLength);
|
||||||
|
MIB_IPFORWARD_ROW2* data = m_clonedRoutes.value(prefix, nullptr);
|
||||||
|
if (data != nullptr) {
|
||||||
|
// Count the number of matching entries in the main table.
|
||||||
|
data->Age++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logger.debug() << "Capturing route to"
|
||||||
|
<< logger.sensitive(prefix.toString());
|
||||||
|
|
||||||
|
// Clone the route and direct it into the VPN tunnel.
|
||||||
|
data = new MIB_IPFORWARD_ROW2;
|
||||||
|
InitializeIpForwardEntry(data);
|
||||||
|
data->InterfaceLuid.Value = m_luid;
|
||||||
|
data->DestinationPrefix = row->DestinationPrefix;
|
||||||
|
data->NextHop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||||
|
|
||||||
|
// Set the rest of the flags for a static route.
|
||||||
|
data->ValidLifetime = 0xffffffff;
|
||||||
|
data->PreferredLifetime = 0xffffffff;
|
||||||
|
data->Metric = 0;
|
||||||
|
data->Protocol = MIB_IPPROTO_NETMGMT;
|
||||||
|
data->Loopback = false;
|
||||||
|
data->AutoconfigureAddress = false;
|
||||||
|
data->Publish = false;
|
||||||
|
data->Immortal = false;
|
||||||
|
data->Age = 0;
|
||||||
|
|
||||||
|
// Route this traffic into the VPN tunnel.
|
||||||
|
DWORD result = CreateIpForwardEntry2(data);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
logger.error() << "Failed to update route:" << result;
|
||||||
|
delete data;
|
||||||
|
} else {
|
||||||
|
m_clonedRoutes.insert(prefix, data);
|
||||||
|
data->Age++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally scan for any routes which were removed from the table. We do this
|
||||||
|
// by reusing the age field to count the number of matching entries in the
|
||||||
|
// main table.
|
||||||
|
auto i = m_clonedRoutes.begin();
|
||||||
|
while (i != m_clonedRoutes.end()) {
|
||||||
|
MIB_IPFORWARD_ROW2* data = i.value();
|
||||||
|
if (data->Age > 0) {
|
||||||
|
// Entry is in use, don't delete it.
|
||||||
|
data->Age = 0;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((family != AF_UNSPEC) &&
|
||||||
|
(data->DestinationPrefix.Prefix.si_family != family)) {
|
||||||
|
// We are not processing updates to this address family.
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug() << "Removing route capture for"
|
||||||
|
<< logger.sensitive(i.key().toString());
|
||||||
|
|
||||||
|
// Otherwise, this route is no longer in use.
|
||||||
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
|
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
||||||
|
logger.error() << "Failed to delete route:" << result;
|
||||||
|
}
|
||||||
|
delete data;
|
||||||
|
i = m_clonedRoutes.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
logger.debug() << "Adding exclusion route for"
|
logger.debug() << "Adding exclusion route for"
|
||||||
<< logger.sensitive(prefix.toString());
|
<< logger.sensitive(prefix.toString());
|
||||||
|
|
||||||
|
// Silently ignore non-routeable addresses.
|
||||||
|
QHostAddress addr = prefix.address();
|
||||||
|
if (addr.isLoopback() || addr.isBroadcast() || addr.isLinkLocal() ||
|
||||||
|
addr.isMulticast()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_exclusionRoutes.contains(prefix)) {
|
if (m_exclusionRoutes.contains(prefix)) {
|
||||||
logger.warning() << "Exclusion route already exists";
|
logger.warning() << "Exclusion route already exists";
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -232,7 +404,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
// Set the rest of the flags for a static route.
|
// Set the rest of the flags for a static route.
|
||||||
data->ValidLifetime = 0xffffffff;
|
data->ValidLifetime = 0xffffffff;
|
||||||
data->PreferredLifetime = 0xffffffff;
|
data->PreferredLifetime = 0xffffffff;
|
||||||
data->Metric = 0;
|
data->Metric = EXCLUSION_ROUTE_METRIC;
|
||||||
data->Protocol = MIB_IPPROTO_NETMGMT;
|
data->Protocol = MIB_IPPROTO_NETMGMT;
|
||||||
data->Loopback = false;
|
data->Loopback = false;
|
||||||
data->AutoconfigureAddress = false;
|
data->AutoconfigureAddress = false;
|
||||||
|
|
@ -254,7 +426,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||||
delete data;
|
delete data;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
updateValidInterfaces(family);
|
updateInterfaceMetrics(family);
|
||||||
|
updateCapturedRoutes(family, table);
|
||||||
updateExclusionRoute(data, table);
|
updateExclusionRoute(data, table);
|
||||||
FreeMibTable(table);
|
FreeMibTable(table);
|
||||||
|
|
||||||
|
|
@ -266,10 +439,9 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
logger.debug() << "Deleting exclusion route for"
|
logger.debug() << "Deleting exclusion route for"
|
||||||
<< logger.sensitive(prefix.address().toString());
|
<< logger.sensitive(prefix.address().toString());
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DWORD result = DeleteIpForwardEntry2(data);
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
|
|
@ -278,14 +450,17 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
<< logger.sensitive(prefix.toString())
|
<< logger.sensitive(prefix.toString())
|
||||||
<< "result:" << result;
|
<< "result:" << result;
|
||||||
}
|
}
|
||||||
delete data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Captured routes might have changed.
|
||||||
|
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
|
||||||
|
|
||||||
|
delete data;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsRouteMonitor::flushExclusionRoutes() {
|
void WindowsRouteMonitor::flushRouteTable(
|
||||||
for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) {
|
QHash<IPAddress, MIB_IPFORWARD_ROW2*>& table) {
|
||||||
|
for (auto i = table.begin(); i != table.end(); i++) {
|
||||||
MIB_IPFORWARD_ROW2* data = i.value();
|
MIB_IPFORWARD_ROW2* data = i.value();
|
||||||
DWORD result = DeleteIpForwardEntry2(data);
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||||
|
|
@ -295,7 +470,17 @@ void WindowsRouteMonitor::flushExclusionRoutes() {
|
||||||
}
|
}
|
||||||
delete data;
|
delete data;
|
||||||
}
|
}
|
||||||
m_exclusionRoutes.clear();
|
table.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsRouteMonitor::setDetaultRouteCapture(bool enable) {
|
||||||
|
m_defaultRouteCapture = enable;
|
||||||
|
|
||||||
|
// Flush any captured routes when disabling the feature.
|
||||||
|
if (!m_defaultRouteCapture) {
|
||||||
|
flushRouteTable(m_clonedRoutes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsRouteMonitor::routeChanged() {
|
void WindowsRouteMonitor::routeChanged() {
|
||||||
|
|
@ -308,7 +493,8 @@ void WindowsRouteMonitor::routeChanged() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateValidInterfaces(AF_UNSPEC);
|
updateInterfaceMetrics(AF_UNSPEC);
|
||||||
|
updateCapturedRoutes(AF_UNSPEC, table);
|
||||||
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
||||||
updateExclusionRoute(data, table);
|
updateExclusionRoute(data, table);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2ipdef.h>
|
#include <ws2ipdef.h>
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
@ -19,28 +21,41 @@ class WindowsRouteMonitor final : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WindowsRouteMonitor(QObject* parent);
|
WindowsRouteMonitor(quint64 luid, QObject* parent);
|
||||||
~WindowsRouteMonitor();
|
~WindowsRouteMonitor();
|
||||||
|
|
||||||
|
void setDetaultRouteCapture(bool enable);
|
||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix);
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
void flushExclusionRoutes();
|
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
|
||||||
|
|
||||||
void setLuid(quint64 luid) { m_luid = luid; }
|
quint64 getLuid() const { return m_luid; }
|
||||||
quint64 getLuid() { return m_luid; }
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void routeChanged();
|
void routeChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
|
||||||
|
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||||
|
const IP_ADDRESS_PREFIX* dest);
|
||||||
|
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
||||||
|
|
||||||
|
void flushRouteTable(QHash<IPAddress, MIB_IPFORWARD_ROW2*>& table);
|
||||||
void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table);
|
void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table);
|
||||||
void updateValidInterfaces(int family);
|
void updateInterfaceMetrics(int family);
|
||||||
|
void updateCapturedRoutes(int family);
|
||||||
|
void updateCapturedRoutes(int family, void* table);
|
||||||
|
|
||||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
||||||
QList<quint64> m_validInterfacesIpv4;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
||||||
QList<quint64> m_validInterfacesIpv6;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
||||||
|
|
||||||
quint64 m_luid = 0;
|
// Default route cloning
|
||||||
|
bool m_defaultRouteCapture = false;
|
||||||
|
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_clonedRoutes;
|
||||||
|
|
||||||
|
const quint64 m_luid = 0;
|
||||||
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,15 @@
|
||||||
|
|
||||||
#include "windowssplittunnel.h"
|
#include "windowssplittunnel.h"
|
||||||
|
|
||||||
|
#include <qassert.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "../windowscommons.h"
|
#include "../windowscommons.h"
|
||||||
#include "../windowsservicemanager.h"
|
#include "../windowsservicemanager.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "platforms/windows/daemon/windowsfirewall.h"
|
||||||
|
#include "platforms/windows/daemon/windowssplittunnel.h"
|
||||||
#include "platforms/windows/windowsutils.h"
|
#include "platforms/windows/windowsutils.h"
|
||||||
#include "windowsfirewall.h"
|
#include "windowsfirewall.h"
|
||||||
|
|
||||||
|
|
@ -18,34 +24,252 @@
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
#include <QThread>
|
|
||||||
|
#pragma region
|
||||||
|
|
||||||
|
// Driver Configuration structures
|
||||||
|
using CONFIGURATION_ENTRY = struct {
|
||||||
|
// Offset into buffer region that follows all entries.
|
||||||
|
// The image name uses the device path.
|
||||||
|
SIZE_T ImageNameOffset;
|
||||||
|
// Length of the String
|
||||||
|
USHORT ImageNameLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
using CONFIGURATION_HEADER = struct {
|
||||||
|
// Number of entries immediately following the header.
|
||||||
|
SIZE_T NumEntries;
|
||||||
|
|
||||||
|
// Total byte length: header + entries + string buffer.
|
||||||
|
SIZE_T TotalLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to Configure Which IP is network/vpn
|
||||||
|
using IP_ADDRESSES_CONFIG = struct {
|
||||||
|
IN_ADDR TunnelIpv4;
|
||||||
|
IN_ADDR InternetIpv4;
|
||||||
|
|
||||||
|
IN6_ADDR TunnelIpv6;
|
||||||
|
IN6_ADDR InternetIpv6;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to Define Which Processes are alive on activation
|
||||||
|
using PROCESS_DISCOVERY_HEADER = struct {
|
||||||
|
SIZE_T NumEntries;
|
||||||
|
SIZE_T TotalLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PROCESS_DISCOVERY_ENTRY = struct {
|
||||||
|
HANDLE ProcessId;
|
||||||
|
HANDLE ParentProcessId;
|
||||||
|
|
||||||
|
SIZE_T ImageNameOffset;
|
||||||
|
USHORT ImageNameLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ProcessInfo = struct {
|
||||||
|
DWORD ProcessId;
|
||||||
|
DWORD ParentProcessId;
|
||||||
|
FILETIME CreationTime;
|
||||||
|
std::wstring DevicePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef CTL_CODE
|
||||||
|
|
||||||
|
# define FILE_ANY_ACCESS 0x0000
|
||||||
|
|
||||||
|
# define METHOD_BUFFERED 0
|
||||||
|
# define METHOD_IN_DIRECT 1
|
||||||
|
# define METHOD_NEITHER 3
|
||||||
|
|
||||||
|
# define CTL_CODE(DeviceType, Function, Method, Access) \
|
||||||
|
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Known ControlCodes
|
||||||
|
#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_DEQUEUE_EVENT \
|
||||||
|
CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_REGISTER_PROCESSES \
|
||||||
|
CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_REGISTER_IP_ADDRESSES \
|
||||||
|
CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_IP_ADDRESSES \
|
||||||
|
CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_SET_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_CLEAR_CONFIGURATION \
|
||||||
|
CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_QUERY_PROCESS \
|
||||||
|
CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
|
||||||
|
constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
|
||||||
|
constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
|
||||||
|
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("WindowsSplitTunnel");
|
Logger logger("WindowsSplitTunnel");
|
||||||
|
|
||||||
|
ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) {
|
||||||
|
ProcessInfo pi;
|
||||||
|
pi.ParentProcessId = processMeta.th32ParentProcessID;
|
||||||
|
pi.ProcessId = processMeta.th32ProcessID;
|
||||||
|
pi.CreationTime = {0, 0};
|
||||||
|
pi.DevicePath = L"";
|
||||||
|
|
||||||
|
FILETIME creationTime, null_time;
|
||||||
|
auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
|
||||||
|
&null_time);
|
||||||
|
if (ok) {
|
||||||
|
pi.CreationTime = creationTime;
|
||||||
|
}
|
||||||
|
wchar_t imagepath[MAX_PATH + 1];
|
||||||
|
if (K32GetProcessImageFileNameW(
|
||||||
|
process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
|
||||||
|
pi.DevicePath = imagepath;
|
||||||
|
}
|
||||||
|
return pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<WindowsSplitTunnel> WindowsSplitTunnel::create(
|
||||||
|
WindowsFirewall* fw) {
|
||||||
|
if (fw == nullptr) {
|
||||||
|
// Pre-Condition:
|
||||||
|
// Make sure the Windows Firewall has created the sublayer
|
||||||
|
// otherwise the driver will fail to initialize
|
||||||
|
logger.error() << "Failed to did not pass a WindowsFirewall obj"
|
||||||
|
<< "The Driver cannot work with the sublayer not created";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// 00: Check if we conflict with mullvad, if so.
|
||||||
if (detectConflict()) {
|
if (detectConflict()) {
|
||||||
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
||||||
uninstallDriver();
|
return nullptr;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// 01: Check if the driver is installed, if not do so.
|
||||||
m_tries = 0;
|
|
||||||
|
|
||||||
if (!isInstalled()) {
|
if (!isInstalled()) {
|
||||||
logger.debug() << "Driver is not Installed, doing so";
|
logger.debug() << "Driver is not Installed, doing so";
|
||||||
auto handle = installDriver();
|
auto handle = installDriver();
|
||||||
if (handle == INVALID_HANDLE_VALUE) {
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
WindowsUtils::windowsLog("Failed to install Driver");
|
WindowsUtils::windowsLog("Failed to install Driver");
|
||||||
return;
|
return nullptr;
|
||||||
}
|
}
|
||||||
logger.debug() << "Driver installed";
|
logger.debug() << "Driver installed";
|
||||||
CloseServiceHandle(handle);
|
CloseServiceHandle(handle);
|
||||||
} else {
|
} else {
|
||||||
logger.debug() << "Driver is installed";
|
logger.debug() << "Driver was installed";
|
||||||
}
|
}
|
||||||
initDriver();
|
// 02: Now check if the service is running
|
||||||
|
auto driver_manager =
|
||||||
|
WindowsServiceManager::open(QString::fromWCharArray(DRIVER_SERVICE_NAME));
|
||||||
|
if (Q_UNLIKELY(driver_manager == nullptr)) {
|
||||||
|
// Let's be fair if we end up here,
|
||||||
|
// after checking it exists and installing it,
|
||||||
|
// this is super unlikeley
|
||||||
|
Q_ASSERT(false);
|
||||||
|
logger.error()
|
||||||
|
<< "WindowsServiceManager was unable fo find Split Tunnel service?";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!driver_manager->isRunning()) {
|
||||||
|
logger.debug() << "Driver is not running, starting it";
|
||||||
|
// Start the service
|
||||||
|
if (!driver_manager->startService()) {
|
||||||
|
logger.error() << "Failed to start Split Tunnel Service";
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 03: Open the Driver Symlink
|
||||||
|
auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||||
|
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
|
;
|
||||||
|
if (driverFile == INVALID_HANDLE_VALUE) {
|
||||||
|
WindowsUtils::windowsLog("Failed to open Driver: ");
|
||||||
|
// Only once, if the opening did not work. Try to reboot it. #
|
||||||
|
logger.info()
|
||||||
|
<< "Failed to open driver, attempting only once to reboot driver";
|
||||||
|
if (!driver_manager->stopService()) {
|
||||||
|
logger.error() << "Unable stop driver";
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
logger.info() << "Stopped driver, starting it again.";
|
||||||
|
if (!driver_manager->startService()) {
|
||||||
|
logger.error() << "Unable start driver";
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
logger.info() << "Opening again.";
|
||||||
|
driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||||
|
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
|
if (driverFile == INVALID_HANDLE_VALUE) {
|
||||||
|
logger.error() << "Opening Failed again, sorry!";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!initDriver(driverFile)) {
|
||||||
|
logger.error() << "Failed to init driver";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// We're ready to talk to the driver, it's alive and setup.
|
||||||
|
return std::make_unique<WindowsSplitTunnel>(driverFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsSplitTunnel::initDriver(HANDLE driverIO) {
|
||||||
|
// We need to now check the state and init it, if required
|
||||||
|
auto state = getState(driverIO);
|
||||||
|
if (state == STATE_UNKNOWN) {
|
||||||
|
logger.debug() << "Cannot check if driver is initialized";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state >= STATE_INITIALIZED) {
|
||||||
|
logger.debug() << "Driver already initialized: " << state;
|
||||||
|
// Reset Driver as it has wfp handles probably >:(
|
||||||
|
resetDriver(driverIO);
|
||||||
|
|
||||||
|
auto newState = getState(driverIO);
|
||||||
|
logger.debug() << "New state after reset:" << newState;
|
||||||
|
if (newState >= STATE_INITIALIZED) {
|
||||||
|
logger.debug() << "Reset unsuccesfull";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD bytesReturned;
|
||||||
|
auto ok = DeviceIoControl(driverIO, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
|
||||||
|
&bytesReturned, nullptr);
|
||||||
|
if (!ok) {
|
||||||
|
auto err = GetLastError();
|
||||||
|
logger.error() << "Driver init failed err -" << err;
|
||||||
|
logger.error() << "State:" << getState(driverIO);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.debug() << "Driver initialized" << getState(driverIO);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsSplitTunnel::WindowsSplitTunnel(HANDLE driverIO) : m_driver(driverIO) {
|
||||||
|
logger.debug() << "Connected to the Driver";
|
||||||
|
|
||||||
|
Q_ASSERT(getState() == STATE_INITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsSplitTunnel::~WindowsSplitTunnel() {
|
WindowsSplitTunnel::~WindowsSplitTunnel() {
|
||||||
|
|
@ -53,73 +277,12 @@ WindowsSplitTunnel::~WindowsSplitTunnel() {
|
||||||
uninstallDriver();
|
uninstallDriver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsSplitTunnel::initDriver() {
|
bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
|
||||||
if (detectConflict()) {
|
|
||||||
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug() << "Try to open Split Tunnel Driver";
|
|
||||||
// Open the Driver Symlink
|
|
||||||
m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
|
||||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
||||||
;
|
|
||||||
if (m_driver == INVALID_HANDLE_VALUE && m_tries < 500) {
|
|
||||||
WindowsUtils::windowsLog("Failed to open Driver: ");
|
|
||||||
m_tries++;
|
|
||||||
Sleep(100);
|
|
||||||
// If the handle is not present, try again after the serivce has started;
|
|
||||||
auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME);
|
|
||||||
QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted,
|
|
||||||
this, &WindowsSplitTunnel::initDriver);
|
|
||||||
driver_manager.startService();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug() << "Connected to the Driver";
|
|
||||||
// Reset Driver as it has wfp handles probably >:(
|
|
||||||
|
|
||||||
if (!WindowsFirewall::instance()->init()) {
|
|
||||||
logger.error() << "Init WFP-Sublayer failed, driver won't be functional";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to now check the state and init it, if required
|
|
||||||
|
|
||||||
auto state = getState();
|
|
||||||
if (state == STATE_UNKNOWN) {
|
|
||||||
logger.debug() << "Cannot check if driver is initialized";
|
|
||||||
}
|
|
||||||
if (state >= STATE_INITIALIZED) {
|
|
||||||
logger.debug() << "Driver already initialized: " << state;
|
|
||||||
reset();
|
|
||||||
|
|
||||||
auto newState = getState();
|
|
||||||
logger.debug() << "New state after reset:" << newState;
|
|
||||||
if (newState >= STATE_INITIALIZED) {
|
|
||||||
logger.debug() << "Reset unsuccesfull";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD bytesReturned;
|
|
||||||
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
|
|
||||||
&bytesReturned, nullptr);
|
|
||||||
if (!ok) {
|
|
||||||
auto err = GetLastError();
|
|
||||||
logger.error() << "Driver init failed err -" << err;
|
|
||||||
logger.error() << "State:" << getState();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug() << "Driver initialized" << getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
|
|
||||||
auto state = getState();
|
auto state = getState();
|
||||||
if (state != STATE_READY && state != STATE_RUNNING) {
|
if (state != STATE_READY && state != STATE_RUNNING) {
|
||||||
logger.warning() << "Driver is not in the right State to set Rules"
|
logger.warning() << "Driver is not in the right State to set Rules"
|
||||||
<< state;
|
<< state;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
|
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
|
||||||
|
|
@ -133,12 +296,13 @@ void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
|
||||||
auto err = GetLastError();
|
auto err = GetLastError();
|
||||||
WindowsUtils::windowsLog("Set Config Failed:");
|
WindowsUtils::windowsLog("Set Config Failed:");
|
||||||
logger.error() << "Failed to set Config err code " << err;
|
logger.error() << "Failed to set Config err code " << err;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "New Configuration applied: " << getState();
|
logger.debug() << "New Configuration applied: " << stateString();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||||
// To Start we need to send 2 things:
|
// To Start we need to send 2 things:
|
||||||
// Network info (what is vpn what is network)
|
// Network info (what is vpn what is network)
|
||||||
logger.debug() << "Starting SplitTunnel";
|
logger.debug() << "Starting SplitTunnel";
|
||||||
|
|
@ -151,7 +315,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||||
0, &bytesReturned, nullptr);
|
0, &bytesReturned, nullptr);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
logger.error() << "Driver init failed";
|
logger.error() << "Driver init failed";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,16 +328,16 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||||
nullptr);
|
nullptr);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
logger.error() << "Failed to set Process Config";
|
logger.error() << "Failed to set Process Config";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "Set Process Config ok || new State:" << getState();
|
logger.debug() << "Set Process Config ok || new State:" << stateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getState() == STATE_INITIALIZED) {
|
if (getState() == STATE_INITIALIZED) {
|
||||||
logger.warning() << "Driver is still not ready after process list send";
|
logger.warning() << "Driver is still not ready after process list send";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "Driver is ready || new State:" << getState();
|
logger.debug() << "Driver is ready || new State:" << stateString();
|
||||||
|
|
||||||
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
|
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
|
||||||
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
|
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
|
||||||
|
|
@ -181,9 +345,10 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||||
nullptr);
|
nullptr);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
logger.error() << "Failed to set Network Config";
|
logger.error() << "Failed to set Network Config";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "New Network Config Applied || new State:" << getState();
|
logger.debug() << "New Network Config Applied || new State:" << stateString();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsSplitTunnel::stop() {
|
void WindowsSplitTunnel::stop() {
|
||||||
|
|
@ -197,25 +362,27 @@ void WindowsSplitTunnel::stop() {
|
||||||
logger.debug() << "Stopping Split tunnel successfull";
|
logger.debug() << "Stopping Split tunnel successfull";
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsSplitTunnel::reset() {
|
bool WindowsSplitTunnel::resetDriver(HANDLE driverIO) {
|
||||||
DWORD bytesReturned;
|
DWORD bytesReturned;
|
||||||
auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
|
auto ok = DeviceIoControl(driverIO, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
|
||||||
&bytesReturned, nullptr);
|
&bytesReturned, nullptr);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
logger.error() << "Reset Split tunnel not successfull";
|
logger.error() << "Reset Split tunnel not successfull";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
logger.debug() << "Reset Split tunnel successfull";
|
logger.debug() << "Reset Split tunnel successfull";
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DRIVER_STATE WindowsSplitTunnel::getState() {
|
// static
|
||||||
if (m_driver == INVALID_HANDLE_VALUE) {
|
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) {
|
||||||
|
if (driverIO == INVALID_HANDLE_VALUE) {
|
||||||
logger.debug() << "Can't query State from non Opened Driver";
|
logger.debug() << "Can't query State from non Opened Driver";
|
||||||
return STATE_UNKNOWN;
|
return STATE_UNKNOWN;
|
||||||
}
|
}
|
||||||
DWORD bytesReturned;
|
DWORD bytesReturned;
|
||||||
SIZE_T outBuffer;
|
SIZE_T outBuffer;
|
||||||
bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
|
bool ok = DeviceIoControl(driverIO, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
|
||||||
sizeof(outBuffer), &bytesReturned, nullptr);
|
sizeof(outBuffer), &bytesReturned, nullptr);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
WindowsUtils::windowsLog("getState response failure");
|
WindowsUtils::windowsLog("getState response failure");
|
||||||
|
|
@ -225,7 +392,10 @@ DRIVER_STATE WindowsSplitTunnel::getState() {
|
||||||
WindowsUtils::windowsLog("getState response is empty");
|
WindowsUtils::windowsLog("getState response is empty");
|
||||||
return STATE_UNKNOWN;
|
return STATE_UNKNOWN;
|
||||||
}
|
}
|
||||||
return static_cast<DRIVER_STATE>(outBuffer);
|
return static_cast<WindowsSplitTunnel::DRIVER_STATE>(outBuffer);
|
||||||
|
}
|
||||||
|
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() {
|
||||||
|
return getState(m_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||||
|
|
@ -273,9 +443,9 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||||
return outBuffer;
|
return outBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
|
std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
|
||||||
int inetAdapterIndex, int vpnAdapterIndex) {
|
int inetAdapterIndex, int vpnAdapterIndex) {
|
||||||
std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG));
|
std::vector<std::byte> out(sizeof(IP_ADDRESSES_CONFIG));
|
||||||
|
|
||||||
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
|
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
|
||||||
|
|
||||||
|
|
@ -284,47 +454,48 @@ std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
|
||||||
if (vpnAdapterIndex == 0) {
|
if (vpnAdapterIndex == 0) {
|
||||||
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
|
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always the VPN
|
// Always the VPN
|
||||||
getAddress(vpnAdapterIndex, &config->TunnelIpv4,
|
if (!getAddress(vpnAdapterIndex, &config->TunnelIpv4,
|
||||||
&config->TunnelIpv6);
|
&config->TunnelIpv6)) {
|
||||||
// 2nd best route
|
return {};
|
||||||
getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
|
}
|
||||||
|
// 2nd best route is usually the internet adapter
|
||||||
|
if (!getAddress(inetAdapterIndex, &config->InternetIpv4,
|
||||||
|
&config->InternetIpv6)) {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||||
IN6_ADDR* out_ipv6) {
|
IN6_ADDR* out_ipv6) {
|
||||||
QNetworkInterface target =
|
QNetworkInterface target =
|
||||||
QNetworkInterface::interfaceFromIndex(adapterIndex);
|
QNetworkInterface::interfaceFromIndex(adapterIndex);
|
||||||
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
|
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
|
||||||
|
|
||||||
// take the first v4/v6 Adress and convert to in_addr
|
auto get = [&target](QAbstractSocket::NetworkLayerProtocol protocol) {
|
||||||
for (auto address : target.addressEntries()) {
|
for (auto address : target.addressEntries()) {
|
||||||
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
if (address.ip().protocol() != protocol) {
|
||||||
auto adrr = address.ip().toString();
|
continue;
|
||||||
std::wstring wstr = adrr.toStdWString();
|
}
|
||||||
logger.debug() << "IpV4" << logger.sensitive(adrr);
|
return address.ip().toString().toStdWString();
|
||||||
PCWSTR w_str_ip = wstr.c_str();
|
}
|
||||||
auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4);
|
return std::wstring{};
|
||||||
if (ok != 1) {
|
};
|
||||||
|
auto ipv4 = get(QAbstractSocket::IPv4Protocol);
|
||||||
|
auto ipv6 = get(QAbstractSocket::IPv6Protocol);
|
||||||
|
|
||||||
|
if (InetPtonW(AF_INET, ipv4.c_str(), out_ipv4) != 1) {
|
||||||
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
|
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
if (ipv6.empty()) {
|
||||||
}
|
std::memset(out_ipv6, 0x00, sizeof(IN6_ADDR));
|
||||||
}
|
return true;
|
||||||
for (auto address : target.addressEntries()) {
|
|
||||||
if (address.ip().protocol() == QAbstractSocket::IPv6Protocol) {
|
|
||||||
auto adrr = address.ip().toString();
|
|
||||||
std::wstring wstr = adrr.toStdWString();
|
|
||||||
logger.debug() << "IpV6" << logger.sensitive(adrr);
|
|
||||||
PCWSTR w_str_ip = wstr.c_str();
|
|
||||||
auto ok = InetPtonW(AF_INET6, w_str_ip, out_ipv6);
|
|
||||||
if (ok != 1) {
|
|
||||||
logger.error() << "Ipv6 Conversation error" << WSAGetLastError();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (InetPtonW(AF_INET6, ipv6.c_str(), out_ipv6) != 1) {
|
||||||
|
logger.debug() << "Ipv6 Conversation error" << WSAGetLastError();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
||||||
|
|
@ -411,33 +582,6 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsSplitTunnel::close() {
|
|
||||||
CloseHandle(m_driver);
|
|
||||||
m_driver = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessInfo WindowsSplitTunnel::getProcessInfo(
|
|
||||||
HANDLE process, const PROCESSENTRY32W& processMeta) {
|
|
||||||
ProcessInfo pi;
|
|
||||||
pi.ParentProcessId = processMeta.th32ParentProcessID;
|
|
||||||
pi.ProcessId = processMeta.th32ProcessID;
|
|
||||||
pi.CreationTime = {0, 0};
|
|
||||||
pi.DevicePath = L"";
|
|
||||||
|
|
||||||
FILETIME creationTime, null_time;
|
|
||||||
auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
|
|
||||||
&null_time);
|
|
||||||
if (ok) {
|
|
||||||
pi.CreationTime = creationTime;
|
|
||||||
}
|
|
||||||
wchar_t imagepath[MAX_PATH + 1];
|
|
||||||
if (K32GetProcessImageFileNameW(
|
|
||||||
process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
|
|
||||||
pi.DevicePath = imagepath;
|
|
||||||
}
|
|
||||||
return pi;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
SC_HANDLE WindowsSplitTunnel::installDriver() {
|
SC_HANDLE WindowsSplitTunnel::installDriver() {
|
||||||
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
|
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
|
||||||
|
|
@ -448,15 +592,15 @@ SC_HANDLE WindowsSplitTunnel::installDriver() {
|
||||||
return (SC_HANDLE)INVALID_HANDLE_VALUE;
|
return (SC_HANDLE)INVALID_HANDLE_VALUE;
|
||||||
}
|
}
|
||||||
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
|
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
|
||||||
LPCWSTR binPath = (const wchar_t*)path.utf16();
|
auto binPath = (const wchar_t*)path.utf16();
|
||||||
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
||||||
auto serviceManager = OpenSCManager(NULL, // local computer
|
auto serviceManager = OpenSCManager(nullptr, // local computer
|
||||||
NULL, // servicesActive database
|
nullptr, // servicesActive database
|
||||||
scm_rights);
|
scm_rights);
|
||||||
auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName,
|
auto service = CreateService(
|
||||||
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
|
serviceManager, DRIVER_SERVICE_NAME, displayName, SERVICE_ALL_ACCESS,
|
||||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binPath,
|
||||||
binPath, nullptr, 0, nullptr, nullptr, nullptr);
|
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||||
CloseServiceHandle(serviceManager);
|
CloseServiceHandle(serviceManager);
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
@ -554,3 +698,25 @@ bool WindowsSplitTunnel::detectConflict() {
|
||||||
CloseServiceHandle(servicehandle);
|
CloseServiceHandle(servicehandle);
|
||||||
return err == ERROR_SERVICE_DOES_NOT_EXIST;
|
return err == ERROR_SERVICE_DOES_NOT_EXIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WindowsSplitTunnel::isRunning() { return getState() == STATE_RUNNING; }
|
||||||
|
QString WindowsSplitTunnel::stateString() {
|
||||||
|
switch (getState()) {
|
||||||
|
case STATE_UNKNOWN:
|
||||||
|
return "STATE_UNKNOWN";
|
||||||
|
case STATE_NONE:
|
||||||
|
return "STATE_NONE";
|
||||||
|
case STATE_STARTED:
|
||||||
|
return "STATE_STARTED";
|
||||||
|
case STATE_INITIALIZED:
|
||||||
|
return "STATE_INITIALIZED";
|
||||||
|
case STATE_READY:
|
||||||
|
return "STATE_READY";
|
||||||
|
case STATE_RUNNING:
|
||||||
|
return "STATE_RUNNING";
|
||||||
|
case STATE_ZOMBIE:
|
||||||
|
return "STATE_ZOMBIE";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// Note: the ws2tcpip.h import must come before the others.
|
// Note: the ws2tcpip.h import must come before the others.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
@ -18,8 +19,47 @@
|
||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
// States for GetState
|
class WindowsFirewall;
|
||||||
enum DRIVER_STATE {
|
|
||||||
|
class WindowsSplitTunnel final {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Installs and Initializes the Split Tunnel Driver.
|
||||||
|
*
|
||||||
|
* @param fw -
|
||||||
|
* @return std::unique_ptr<WindowsSplitTunnel> - Is null on failure.
|
||||||
|
*/
|
||||||
|
static std::unique_ptr<WindowsSplitTunnel> create(WindowsFirewall* fw);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Windows Split Tunnel object
|
||||||
|
*
|
||||||
|
* @param driverIO - The Handle to the Driver's IO file, it assumes the driver
|
||||||
|
* is in STATE_INITIALIZED and the Firewall has been setup.
|
||||||
|
* Prefer using create() to get to this state.
|
||||||
|
*/
|
||||||
|
WindowsSplitTunnel(HANDLE driverIO);
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Windows Split Tunnel object and uninstalls the Driver.
|
||||||
|
*/
|
||||||
|
~WindowsSplitTunnel();
|
||||||
|
|
||||||
|
// void excludeApps(const QStringList& paths);
|
||||||
|
// Excludes an Application from the VPN
|
||||||
|
bool excludeApps(const QStringList& appPaths);
|
||||||
|
|
||||||
|
// Fetches and Pushed needed info to move to engaged mode
|
||||||
|
bool start(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||||
|
// Deletes Rules and puts the driver into passive mode
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
// Returns true if the split-tunnel driver is now up and running.
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
static bool detectConflict();
|
||||||
|
|
||||||
|
// States for GetState
|
||||||
|
enum DRIVER_STATE {
|
||||||
STATE_UNKNOWN = -1,
|
STATE_UNKNOWN = -1,
|
||||||
STATE_NONE = 0,
|
STATE_NONE = 0,
|
||||||
STATE_STARTED = 1,
|
STATE_STARTED = 1,
|
||||||
|
|
@ -27,151 +67,30 @@ enum DRIVER_STATE {
|
||||||
STATE_READY = 3,
|
STATE_READY = 3,
|
||||||
STATE_RUNNING = 4,
|
STATE_RUNNING = 4,
|
||||||
STATE_ZOMBIE = 5,
|
STATE_ZOMBIE = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef CTL_CODE
|
|
||||||
|
|
||||||
# define FILE_ANY_ACCESS 0x0000
|
|
||||||
|
|
||||||
# define METHOD_BUFFERED 0
|
|
||||||
# define METHOD_IN_DIRECT 1
|
|
||||||
# define METHOD_NEITHER 3
|
|
||||||
|
|
||||||
# define CTL_CODE(DeviceType, Function, Method, Access) \
|
|
||||||
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Known ControlCodes
|
|
||||||
#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_DEQUEUE_EVENT \
|
|
||||||
CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_REGISTER_PROCESSES \
|
|
||||||
CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_REGISTER_IP_ADDRESSES \
|
|
||||||
CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_GET_IP_ADDRESSES \
|
|
||||||
CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_SET_CONFIGURATION \
|
|
||||||
CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_GET_CONFIGURATION \
|
|
||||||
CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_CLEAR_CONFIGURATION \
|
|
||||||
CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_QUERY_PROCESS \
|
|
||||||
CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
|
||||||
|
|
||||||
// Driver Configuration structures
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
// Offset into buffer region that follows all entries.
|
|
||||||
// The image name uses the device path.
|
|
||||||
SIZE_T ImageNameOffset;
|
|
||||||
// Length of the String
|
|
||||||
USHORT ImageNameLength;
|
|
||||||
} CONFIGURATION_ENTRY;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
// Number of entries immediately following the header.
|
|
||||||
SIZE_T NumEntries;
|
|
||||||
|
|
||||||
// Total byte length: header + entries + string buffer.
|
|
||||||
SIZE_T TotalLength;
|
|
||||||
} CONFIGURATION_HEADER;
|
|
||||||
|
|
||||||
// Used to Configure Which IP is network/vpn
|
|
||||||
typedef struct {
|
|
||||||
IN_ADDR TunnelIpv4;
|
|
||||||
IN_ADDR InternetIpv4;
|
|
||||||
|
|
||||||
IN6_ADDR TunnelIpv6;
|
|
||||||
IN6_ADDR InternetIpv6;
|
|
||||||
} IP_ADDRESSES_CONFIG;
|
|
||||||
|
|
||||||
// Used to Define Which Processes are alive on activation
|
|
||||||
typedef struct {
|
|
||||||
SIZE_T NumEntries;
|
|
||||||
SIZE_T TotalLength;
|
|
||||||
} PROCESS_DISCOVERY_HEADER;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
HANDLE ProcessId;
|
|
||||||
HANDLE ParentProcessId;
|
|
||||||
|
|
||||||
SIZE_T ImageNameOffset;
|
|
||||||
USHORT ImageNameLength;
|
|
||||||
} PROCESS_DISCOVERY_ENTRY;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
DWORD ProcessId;
|
|
||||||
DWORD ParentProcessId;
|
|
||||||
FILETIME CreationTime;
|
|
||||||
std::wstring DevicePath;
|
|
||||||
} ProcessInfo;
|
|
||||||
|
|
||||||
class WindowsSplitTunnel final : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DISABLE_COPY_MOVE(WindowsSplitTunnel)
|
|
||||||
public:
|
|
||||||
explicit WindowsSplitTunnel(QObject* parent);
|
|
||||||
~WindowsSplitTunnel();
|
|
||||||
|
|
||||||
// void excludeApps(const QStringList& paths);
|
|
||||||
// Excludes an Application from the VPN
|
|
||||||
void setRules(const QStringList& appPaths);
|
|
||||||
|
|
||||||
// Fetches and Pushed needed info to move to engaged mode
|
|
||||||
void start(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
|
||||||
// Deletes Rules and puts the driver into passive mode
|
|
||||||
void stop();
|
|
||||||
// Resets the Whole Driver
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
// Just close connection, leave state as is
|
|
||||||
void close();
|
|
||||||
|
|
||||||
|
private:
|
||||||
// Installes the Kernel Driver as Driver Service
|
// Installes the Kernel Driver as Driver Service
|
||||||
static SC_HANDLE installDriver();
|
static SC_HANDLE installDriver();
|
||||||
static bool uninstallDriver();
|
static bool uninstallDriver();
|
||||||
static bool isInstalled();
|
static bool isInstalled();
|
||||||
static bool detectConflict();
|
static bool initDriver(HANDLE driverIO);
|
||||||
|
static DRIVER_STATE getState(HANDLE driverIO);
|
||||||
|
static bool resetDriver(HANDLE driverIO);
|
||||||
|
|
||||||
private slots:
|
|
||||||
void initDriver();
|
|
||||||
|
|
||||||
private:
|
|
||||||
HANDLE m_driver = INVALID_HANDLE_VALUE;
|
HANDLE m_driver = INVALID_HANDLE_VALUE;
|
||||||
constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
|
|
||||||
constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
|
|
||||||
constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
|
|
||||||
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
|
||||||
DRIVER_STATE getState();
|
DRIVER_STATE getState();
|
||||||
|
QString stateString();
|
||||||
int m_tries;
|
|
||||||
// Initializes the WFP Sublayer
|
|
||||||
bool initSublayer();
|
|
||||||
|
|
||||||
// Generates a Configuration for Each APP
|
// Generates a Configuration for Each APP
|
||||||
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
|
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
|
||||||
// Generates a Configuration which IP's are VPN and which network
|
// Generates a Configuration which IP's are VPN and which network
|
||||||
std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
std::vector<std::byte> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||||
std::vector<uint8_t> generateProcessBlob();
|
std::vector<uint8_t> generateProcessBlob();
|
||||||
|
|
||||||
void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
|
[[nodiscard]] bool getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||||
|
IN6_ADDR* out_ipv6);
|
||||||
// Collects info about an Opened Process
|
// Collects info about an Opened Process
|
||||||
ProcessInfo getProcessInfo(HANDLE process,
|
|
||||||
const PROCESSENTRY32W& processMeta);
|
|
||||||
|
|
||||||
// Converts a path to a Dos Path:
|
// Converts a path to a Dos Path:
|
||||||
// e.g C:/a.exe -> /harddisk0/a.exe
|
// e.g C:/a.exe -> /harddisk0/a.exe
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,20 @@ namespace {
|
||||||
Logger logger("WireguardUtilsWindows");
|
Logger logger("WireguardUtilsWindows");
|
||||||
}; // namespace
|
}; // namespace
|
||||||
|
|
||||||
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent)
|
std::unique_ptr<WireguardUtilsWindows> WireguardUtilsWindows::create(
|
||||||
: WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) {
|
WindowsFirewall* fw, QObject* parent) {
|
||||||
|
if (!fw) {
|
||||||
|
logger.error() << "WireguardUtilsWindows::create: no wfp handle";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use make_unique here as the Constructor is private :(
|
||||||
|
auto utils = new WireguardUtilsWindows(parent, fw);
|
||||||
|
return std::unique_ptr<WireguardUtilsWindows>(utils);
|
||||||
|
}
|
||||||
|
|
||||||
|
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw)
|
||||||
|
: WireguardUtils(parent), m_tunnel(this), m_firewall(fw) {
|
||||||
MZ_COUNT_CTOR(WireguardUtilsWindows);
|
MZ_COUNT_CTOR(WireguardUtilsWindows);
|
||||||
logger.debug() << "WireguardUtilsWindows created.";
|
logger.debug() << "WireguardUtilsWindows created.";
|
||||||
|
|
||||||
|
|
@ -114,13 +126,13 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_luid = luid.Value;
|
m_luid = luid.Value;
|
||||||
m_routeMonitor.setLuid(luid.Value);
|
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
|
||||||
|
|
||||||
if (config.m_killSwitchEnabled) {
|
if (config.m_killSwitchEnabled) {
|
||||||
// Enable the windows firewall
|
// Enable the windows firewall
|
||||||
NET_IFINDEX ifindex;
|
NET_IFINDEX ifindex;
|
||||||
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||||
WindowsFirewall::instance()->enableKillSwitch(ifindex);
|
m_firewall->enableInterface(ifindex);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "Registration completed";
|
logger.debug() << "Registration completed";
|
||||||
|
|
@ -128,7 +140,11 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::deleteInterface() {
|
bool WireguardUtilsWindows::deleteInterface() {
|
||||||
WindowsFirewall::instance()->disableKillSwitch();
|
if (m_routeMonitor) {
|
||||||
|
m_routeMonitor->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_firewall->disableKillSwitch();
|
||||||
m_tunnel.stop();
|
m_tunnel.stop();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +157,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||||
|
|
||||||
if (config.m_killSwitchEnabled) {
|
if (config.m_killSwitchEnabled) {
|
||||||
// Enable the windows firewall for this peer.
|
// Enable the windows firewall for this peer.
|
||||||
WindowsFirewall::instance()->enablePeerTraffic(config);
|
m_firewall->enablePeerTraffic(config);
|
||||||
}
|
}
|
||||||
logger.debug() << "Configuring peer" << publicKey.toHex()
|
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||||
<< "via" << config.m_serverIpv4AddrIn;
|
<< "via" << config.m_serverIpv4AddrIn;
|
||||||
|
|
@ -171,9 +187,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude the server address, except for multihop exit servers.
|
// Exclude the server address, except for multihop exit servers.
|
||||||
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString reply = m_tunnel.uapiCommand(message);
|
QString reply = m_tunnel.uapiCommand(message);
|
||||||
|
|
@ -186,13 +202,13 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
// Clear exclustion routes for this peer.
|
// Clear exclustion routes for this peer.
|
||||||
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable the windows firewall for this peer.
|
// Disable the windows firewall for this peer.
|
||||||
WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey);
|
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
|
|
@ -238,6 +254,13 @@ void WireguardUtilsWindows::buildMibForwardRow(const IPAddress& prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
|
if (m_routeMonitor && (prefix.prefixLength() == 0)) {
|
||||||
|
// If we are setting up a default route, instruct the route monitor to
|
||||||
|
// capture traffic to all non-excluded destinations
|
||||||
|
m_routeMonitor->setDetaultRouteCapture(true);
|
||||||
|
}
|
||||||
|
// Build the route
|
||||||
|
|
||||||
MIB_IPFORWARD_ROW2 entry;
|
MIB_IPFORWARD_ROW2 entry;
|
||||||
buildMibForwardRow(prefix, &entry);
|
buildMibForwardRow(prefix, &entry);
|
||||||
|
|
||||||
|
|
@ -255,6 +278,12 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
|
if (m_routeMonitor && (prefix.prefixLength() == 0)) {
|
||||||
|
// Deactivate the route capture feature.
|
||||||
|
m_routeMonitor->setDetaultRouteCapture(false);
|
||||||
|
}
|
||||||
|
// Build the route
|
||||||
|
|
||||||
MIB_IPFORWARD_ROW2 entry;
|
MIB_IPFORWARD_ROW2 entry;
|
||||||
buildMibForwardRow(prefix, &entry);
|
buildMibForwardRow(prefix, &entry);
|
||||||
|
|
||||||
|
|
@ -272,9 +301,28 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) {
|
bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) {
|
||||||
return m_routeMonitor.addExclusionRoute(prefix);
|
return m_routeMonitor->addExclusionRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) {
|
bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) {
|
||||||
return m_routeMonitor.deleteExclusionRoute(prefix);
|
return m_routeMonitor->deleteExclusionRoute(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::excludeLocalNetworks(
|
||||||
|
const QList<IPAddress>& addresses) {
|
||||||
|
// If the interface isn't up then something went horribly wrong.
|
||||||
|
Q_ASSERT(m_routeMonitor);
|
||||||
|
// For each destination - attempt to exclude it from the VPN tunnel.
|
||||||
|
bool result = true;
|
||||||
|
for (const IPAddress& prefix : addresses) {
|
||||||
|
if (!m_routeMonitor->addExclusionRoute(prefix)) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Permit LAN traffic through the firewall.
|
||||||
|
if (!m_firewall->enableLanBypass(addresses)) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,21 @@
|
||||||
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include "daemon/wireguardutils.h"
|
#include "daemon/wireguardutils.h"
|
||||||
#include "windowsroutemonitor.h"
|
#include "windowsroutemonitor.h"
|
||||||
#include "windowstunnelservice.h"
|
#include "windowstunnelservice.h"
|
||||||
|
|
||||||
|
class WindowsFirewall;
|
||||||
|
class WindowsRouteMonitor;
|
||||||
|
|
||||||
class WireguardUtilsWindows final : public WireguardUtils {
|
class WireguardUtilsWindows final : public WireguardUtils {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WireguardUtilsWindows(QObject* parent);
|
static std::unique_ptr<WireguardUtilsWindows> create(WindowsFirewall* fw,
|
||||||
|
QObject* parent);
|
||||||
~WireguardUtilsWindows();
|
~WireguardUtilsWindows();
|
||||||
|
|
||||||
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
||||||
|
|
@ -39,15 +44,19 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
||||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||||
|
|
||||||
|
bool WireguardUtilsWindows::excludeLocalNetworks(const QList<IPAddress>& addresses) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw);
|
||||||
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
||||||
|
|
||||||
quint64 m_luid = 0;
|
quint64 m_luid = 0;
|
||||||
WindowsTunnelService m_tunnel;
|
WindowsTunnelService m_tunnel;
|
||||||
WindowsRouteMonitor m_routeMonitor;
|
QPointer<WindowsRouteMonitor> m_routeMonitor;
|
||||||
|
QPointer<WindowsFirewall> m_firewall;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WIREGUARDUTILSWINDOWS_H
|
#endif // WIREGUARDUTILSWINDOWS_H
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "windowsservicemanager.h"
|
#include "windowsservicemanager.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "Windows.h"
|
#include "Windows.h"
|
||||||
|
|
@ -16,35 +17,44 @@ namespace {
|
||||||
Logger logger("WindowsServiceManager");
|
Logger logger("WindowsServiceManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) {
|
WindowsServiceManager::WindowsServiceManager(SC_HANDLE serviceManager,
|
||||||
|
SC_HANDLE service)
|
||||||
|
: QObject(qApp), m_serviceManager(serviceManager), m_service(service) {
|
||||||
|
m_timer.setSingleShot(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<WindowsServiceManager> WindowsServiceManager::open(
|
||||||
|
const QString serviceName) {
|
||||||
|
LPCWSTR service = (const wchar_t*)serviceName.utf16();
|
||||||
|
|
||||||
DWORD err = NULL;
|
DWORD err = NULL;
|
||||||
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
|
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
|
||||||
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
|
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
|
||||||
m_serviceManager = OpenSCManager(NULL, // local computer
|
auto manager = OpenSCManager(NULL, // local computer
|
||||||
NULL, // servicesActive database
|
NULL, // servicesActive database
|
||||||
scm_rights);
|
scm_rights);
|
||||||
err = GetLastError();
|
err = GetLastError();
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
logger.error() << " OpenSCManager failed code: " << err;
|
logger.error() << " OpenSCManager failed code: " << err;
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
logger.debug() << "OpenSCManager access given - " << err;
|
logger.debug() << "OpenSCManager access given - " << err;
|
||||||
|
|
||||||
logger.debug() << "Opening Service - "
|
logger.debug() << "Opening Service - " << serviceName;
|
||||||
<< QString::fromWCharArray(serviceName);
|
|
||||||
// Try to get an elevated handle
|
// Try to get an elevated handle
|
||||||
m_service = OpenService(m_serviceManager, // SCM database
|
auto serviceHandle =
|
||||||
serviceName, // name of service
|
OpenService(manager, // SCM database
|
||||||
|
service, // name of service
|
||||||
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
|
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
|
||||||
err = GetLastError();
|
err = GetLastError();
|
||||||
if (err != NULL) {
|
if (err != NULL) {
|
||||||
|
CloseServiceHandle(manager);
|
||||||
WindowsUtils::windowsLog("OpenService failed");
|
WindowsUtils::windowsLog("OpenService failed");
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
m_has_access = true;
|
|
||||||
m_timer.setSingleShot(false);
|
|
||||||
|
|
||||||
logger.debug() << "Service manager execute access granted";
|
logger.debug() << "Service manager execute access granted";
|
||||||
|
return std::make_unique<WindowsServiceManager>(manager, serviceHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsServiceManager::~WindowsServiceManager() {
|
WindowsServiceManager::~WindowsServiceManager() {
|
||||||
|
|
@ -85,10 +95,6 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
|
||||||
|
|
||||||
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
|
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
|
||||||
SERVICE_STATUS_PROCESS serviceStatus;
|
SERVICE_STATUS_PROCESS serviceStatus;
|
||||||
if (!m_has_access) {
|
|
||||||
logger.debug() << "Need read access to get service state";
|
|
||||||
return serviceStatus;
|
|
||||||
}
|
|
||||||
DWORD dwBytesNeeded; // Contains missing bytes if struct is too small?
|
DWORD dwBytesNeeded; // Contains missing bytes if struct is too small?
|
||||||
QueryServiceStatusEx(m_service, // handle to service
|
QueryServiceStatusEx(m_service, // handle to service
|
||||||
SC_STATUS_PROCESS_INFO, // information level
|
SC_STATUS_PROCESS_INFO, // information level
|
||||||
|
|
@ -119,10 +125,6 @@ bool WindowsServiceManager::startService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsServiceManager::stopService() {
|
bool WindowsServiceManager::stopService() {
|
||||||
if (!m_has_access) {
|
|
||||||
logger.error() << "Need execute access to stop services";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto state = getStatus().dwCurrentState;
|
auto state = getStatus().dwCurrentState;
|
||||||
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
|
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
|
||||||
logger.warning() << ("Service stop not possible, as its not running");
|
logger.warning() << ("Service stop not possible, as its not running");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
#include "Winsvc.h"
|
#include "Winsvc.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The WindowsServiceManager provides control over the MozillaVPNBroker
|
* @brief The WindowsServiceManager provides control over the a
|
||||||
* service via SCM
|
* service via SCM
|
||||||
*/
|
*/
|
||||||
class WindowsServiceManager : public QObject {
|
class WindowsServiceManager : public QObject {
|
||||||
|
|
@ -20,7 +20,10 @@ class WindowsServiceManager : public QObject {
|
||||||
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
|
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WindowsServiceManager(LPCWSTR serviceName);
|
// Creates a WindowsServiceManager for the Named service.
|
||||||
|
// returns nullptr if
|
||||||
|
static std::unique_ptr<WindowsServiceManager> open(const QString serviceName);
|
||||||
|
WindowsServiceManager(SC_HANDLE serviceManager, SC_HANDLE service);
|
||||||
~WindowsServiceManager();
|
~WindowsServiceManager();
|
||||||
|
|
||||||
// true if the Service is running
|
// true if the Service is running
|
||||||
|
|
@ -45,8 +48,6 @@ class WindowsServiceManager : public QObject {
|
||||||
// See
|
// See
|
||||||
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
|
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
|
||||||
SERVICE_STATUS_PROCESS getStatus();
|
SERVICE_STATUS_PROCESS getStatus();
|
||||||
bool m_has_access = false;
|
|
||||||
LPWSTR m_serviceName;
|
|
||||||
SC_HANDLE m_serviceManager;
|
SC_HANDLE m_serviceManager;
|
||||||
SC_HANDLE m_service; // Service handle with r/w priv.
|
SC_HANDLE m_service; // Service handle with r/w priv.
|
||||||
DWORD m_state_target;
|
DWORD m_state_target;
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ ErrorCode Ikev2Protocol::start()
|
||||||
"-CipherTransformConstants GCMAES128 "
|
"-CipherTransformConstants GCMAES128 "
|
||||||
"-EncryptionMethod AES256 "
|
"-EncryptionMethod AES256 "
|
||||||
"-IntegrityCheckMethod SHA256 "
|
"-IntegrityCheckMethod SHA256 "
|
||||||
"-PfsGroup None "
|
"-PfsGroup PFS2048 "
|
||||||
"-DHGroup Group14 "
|
"-DHGroup Group14 "
|
||||||
"-PassThru -Force\"")
|
"-PassThru -Force\"")
|
||||||
.arg(tunnelName());
|
.arg(tunnelName());
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
#include "xrayprotocol.h"
|
#include "xrayprotocol.h"
|
||||||
|
|
||||||
#include "utilities.h"
|
|
||||||
#include "containers/containers_defs.h"
|
|
||||||
#include "core/networkUtilities.h"
|
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
|
||||||
|
#include "core/networkUtilities.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
|
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||||
VpnProtocol(configuration, parent)
|
|
||||||
{
|
{
|
||||||
readXrayConfiguration(configuration);
|
readXrayConfiguration(configuration);
|
||||||
m_routeGateway = NetworkUtilities::getGatewayAndIface();
|
m_routeGateway = NetworkUtilities::getGatewayAndIface();
|
||||||
|
|
@ -22,9 +19,8 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
|
||||||
|
|
||||||
XrayProtocol::~XrayProtocol()
|
XrayProtocol::~XrayProtocol()
|
||||||
{
|
{
|
||||||
|
qDebug() << "XrayProtocol::~XrayProtocol()";
|
||||||
XrayProtocol::stop();
|
XrayProtocol::stop();
|
||||||
QThread::msleep(200);
|
|
||||||
m_xrayProcess.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode XrayProtocol::start()
|
ErrorCode XrayProtocol::start()
|
||||||
|
|
@ -36,10 +32,6 @@ ErrorCode XrayProtocol::start()
|
||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
|
|
||||||
Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
m_xrayCfgFile.setAutoRemove(false);
|
m_xrayCfgFile.setAutoRemove(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -51,12 +43,16 @@ ErrorCode XrayProtocol::start()
|
||||||
|
|
||||||
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
|
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
|
||||||
|
|
||||||
qDebug().noquote() << "XrayProtocol::start()"
|
qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" ");
|
||||||
<< xrayExecPath() << args.join(" ");
|
|
||||||
|
|
||||||
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
|
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
|
||||||
m_xrayProcess.setProgram(xrayExecPath());
|
m_xrayProcess.setProgram(xrayExecPath());
|
||||||
|
|
||||||
|
if (Utils::processIsRunning(Utils::executable("xray", false))) {
|
||||||
|
qDebug().noquote() << "kill previos xray";
|
||||||
|
Utils::killProcessByName(Utils::executable("xray", false));
|
||||||
|
}
|
||||||
|
|
||||||
m_xrayProcess.setArguments(args);
|
m_xrayProcess.setArguments(args);
|
||||||
|
|
||||||
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||||
|
|
@ -65,16 +61,13 @@ ErrorCode XrayProtocol::start()
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
|
||||||
|
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||||
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
|
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
if (exitStatus != QProcess::NormalExit) {
|
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
|
||||||
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
|
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
|
||||||
stop();
|
emit setConnectionState(Vpn::ConnectionState::Error);
|
||||||
}
|
|
||||||
if (exitCode != 0) {
|
|
||||||
emit protocolError(amnezia::ErrorCode::InternalError);
|
|
||||||
stop();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -85,11 +78,10 @@ ErrorCode XrayProtocol::start()
|
||||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
QThread::msleep(1000);
|
QThread::msleep(1000);
|
||||||
return startTun2Sock();
|
return startTun2Sock();
|
||||||
}
|
} else
|
||||||
else return ErrorCode::XrayExecutableMissing;
|
return ErrorCode::XrayExecutableMissing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ErrorCode XrayProtocol::startTun2Sock()
|
ErrorCode XrayProtocol::startTun2Sock()
|
||||||
{
|
{
|
||||||
m_t2sProcess->start();
|
m_t2sProcess->start();
|
||||||
|
|
@ -101,11 +93,9 @@ ErrorCode XrayProtocol::startTun2Sock()
|
||||||
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
|
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
|
||||||
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
||||||
|
|
||||||
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this,
|
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
|
||||||
[&](int vpnState) {
|
|
||||||
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
|
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
|
||||||
if (vpnState == Vpn::ConnectionState::Connected)
|
if (vpnState == Vpn::ConnectionState::Connected) {
|
||||||
{
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
QList<QHostAddress> dnsAddr;
|
QList<QHostAddress> dnsAddr;
|
||||||
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
|
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
|
||||||
|
|
@ -130,7 +120,7 @@ ErrorCode XrayProtocol::startTun2Sock()
|
||||||
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
|
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (m_routeMode == 0) {
|
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
|
||||||
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1");
|
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1");
|
||||||
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
|
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
|
||||||
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
|
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
|
||||||
|
|
@ -140,8 +130,7 @@ ErrorCode XrayProtocol::startTun2Sock()
|
||||||
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
|
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
|
||||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||||
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) {
|
||||||
{
|
|
||||||
// killSwitch toggle
|
// killSwitch toggle
|
||||||
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||||
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
|
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
|
||||||
|
|
@ -177,14 +166,14 @@ void XrayProtocol::stop()
|
||||||
IpcClient::Interface()->StartRoutingIpv6();
|
IpcClient::Interface()->StartRoutingIpv6();
|
||||||
#endif
|
#endif
|
||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
m_xrayProcess.terminate();
|
m_xrayProcess.disconnect();
|
||||||
|
m_xrayProcess.kill();
|
||||||
|
m_xrayProcess.waitForFinished(3000);
|
||||||
if (m_t2sProcess) {
|
if (m_t2sProcess) {
|
||||||
m_t2sProcess->stop();
|
m_t2sProcess->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString XrayProtocol::xrayExecPath()
|
QString XrayProtocol::xrayExecPath()
|
||||||
|
|
@ -207,7 +196,7 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
|
||||||
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
||||||
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
|
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
|
||||||
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
|
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
|
||||||
m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt();
|
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
|
||||||
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
|
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
|
||||||
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
|
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
#ifndef XRAYPROTOCOL_H
|
#ifndef XRAYPROTOCOL_H
|
||||||
#define XRAYPROTOCOL_H
|
#define XRAYPROTOCOL_H
|
||||||
|
|
||||||
#include "openvpnprotocol.h"
|
|
||||||
#include "QProcess"
|
#include "QProcess"
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "openvpnprotocol.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
class XrayProtocol : public VpnProtocol
|
class XrayProtocol : public VpnProtocol
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
XrayProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
XrayProtocol(const QJsonObject &configuration, QObject *parent = nullptr);
|
||||||
virtual ~XrayProtocol() override;
|
virtual ~XrayProtocol() override;
|
||||||
|
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
|
|
@ -24,11 +26,12 @@ protected:
|
||||||
private:
|
private:
|
||||||
static QString xrayExecPath();
|
static QString xrayExecPath();
|
||||||
static QString tun2SocksExecPath();
|
static QString tun2SocksExecPath();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_localPort;
|
int m_localPort;
|
||||||
QString m_remoteHost;
|
QString m_remoteHost;
|
||||||
QString m_remoteAddress;
|
QString m_remoteAddress;
|
||||||
int m_routeMode;
|
Settings::RouteMode m_routeMode;
|
||||||
QJsonObject m_configData;
|
QJsonObject m_configData;
|
||||||
QString m_primaryDNS;
|
QString m_primaryDNS;
|
||||||
QString m_secondaryDNS;
|
QString m_secondaryDNS;
|
||||||
|
|
@ -37,7 +40,6 @@ private:
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
|
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
|
||||||
#endif
|
#endif
|
||||||
QTemporaryFile m_xrayCfgFile;
|
QTemporaryFile m_xrayCfgFile;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // XRAYPROTOCOL_H
|
#endif // XRAYPROTOCOL_H
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
<file>images/controls/edit-3.svg</file>
|
<file>images/controls/edit-3.svg</file>
|
||||||
<file>images/controls/eye-off.svg</file>
|
<file>images/controls/eye-off.svg</file>
|
||||||
<file>images/controls/eye.svg</file>
|
<file>images/controls/eye.svg</file>
|
||||||
|
<file>images/controls/external-link.svg</file>
|
||||||
<file>images/controls/file-check-2.svg</file>
|
<file>images/controls/file-check-2.svg</file>
|
||||||
<file>images/controls/file-cog-2.svg</file>
|
<file>images/controls/file-cog-2.svg</file>
|
||||||
<file>images/controls/folder-open.svg</file>
|
<file>images/controls/folder-open.svg</file>
|
||||||
|
|
@ -116,6 +117,7 @@
|
||||||
<file>server_scripts/xray/run_container.sh</file>
|
<file>server_scripts/xray/run_container.sh</file>
|
||||||
<file>server_scripts/xray/start.sh</file>
|
<file>server_scripts/xray/start.sh</file>
|
||||||
<file>server_scripts/xray/template.json</file>
|
<file>server_scripts/xray/template.json</file>
|
||||||
|
<file>ui/qml/Components/AdLabel.qml</file>
|
||||||
<file>ui/qml/Components/ConnectButton.qml</file>
|
<file>ui/qml/Components/ConnectButton.qml</file>
|
||||||
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
||||||
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@
|
||||||
|
|
||||||
using namespace QKeychain;
|
using namespace QKeychain;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr const char *settingsKeyTag = "settingsKeyTag";
|
||||||
|
constexpr const char *settingsIvTag = "settingsIvTag";
|
||||||
|
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
|
||||||
|
}
|
||||||
|
|
||||||
SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent)
|
SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent)
|
||||||
: QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" })
|
: QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" })
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +55,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
|
||||||
// check if value is not encrypted, v. < 2.0.x
|
// check if value is not encrypted, v. < 2.0.x
|
||||||
retVal = m_settings.value(key);
|
retVal = m_settings.value(key);
|
||||||
if (retVal.isValid()) {
|
if (retVal.isValid()) {
|
||||||
if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) {
|
if (retVal.userType() == QMetaType::QByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) {
|
||||||
|
|
||||||
if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
|
if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
|
||||||
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";
|
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,6 @@
|
||||||
|
|
||||||
#include "../client/3rd/qtkeychain/qtkeychain/keychain.h"
|
#include "../client/3rd/qtkeychain/qtkeychain/keychain.h"
|
||||||
|
|
||||||
constexpr const char *settingsKeyTag = "settingsKeyTag";
|
|
||||||
constexpr const char *settingsIvTag = "settingsIvTag";
|
|
||||||
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
|
|
||||||
|
|
||||||
class SecureQSettings : public QObject
|
class SecureQSettings : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -44,7 +40,7 @@ public:
|
||||||
private:
|
private:
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
|
|
||||||
mutable QMap<QString, QVariant> m_cache;
|
mutable QHash<QString, QVariant> m_cache;
|
||||||
|
|
||||||
QStringList encryptedKeys; // encode only key listed here
|
QStringList encryptedKeys; // encode only key listed here
|
||||||
// only this fields need for backup
|
// only this fields need for backup
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
|
||||||
cat > /opt/amnezia/awg/wg0.conf <<EOF
|
cat > /opt/amnezia/awg/wg0.conf <<EOF
|
||||||
[Interface]
|
[Interface]
|
||||||
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
|
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
|
||||||
Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
|
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
|
||||||
ListenPort = $AWG_SERVER_PORT
|
ListenPort = $AWG_SERVER_PORT
|
||||||
Jc = $JUNK_PACKET_COUNT
|
Jc = $JUNK_PACKET_COUNT
|
||||||
Jmin = $JUNK_PACKET_MIN_SIZE
|
Jmin = $JUNK_PACKET_MIN_SIZE
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ iptables -A FORWARD -i wg0 -j ACCEPT
|
||||||
iptables -A OUTPUT -o wg0 -j ACCEPT
|
iptables -A OUTPUT -o wg0 -j ACCEPT
|
||||||
|
|
||||||
# Allow forwarding traffic only from the VPN.
|
# Allow forwarding traffic only from the VPN.
|
||||||
iptables -A FORWARD -i wg0 -o eth0 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||||
iptables -A FORWARD -i wg0 -o eth1 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||||
|
|
||||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
|
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
|
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||||
|
|
||||||
tail -f /dev/null
|
tail -f /dev/null
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq 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 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 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 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 which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\
|
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";\
|
||||||
else echo "Packet manager not found"; exit 1; fi;\
|
else echo "Packet manager not found"; exit 1; 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, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
|
||||||
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||||
|
|
@ -12,6 +12,9 @@ if ! command -v docker > /dev/null 2>&1; then \
|
||||||
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||||
sleep 5; sudo systemctl enable --now docker; sleep 5;\
|
sleep 5; sudo systemctl enable --now docker; sleep 5;\
|
||||||
fi;\
|
fi;\
|
||||||
|
if [ "$(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;\
|
||||||
|
fi;\
|
||||||
if [ "$(systemctl is-active docker)" != "active" ]; then \
|
if [ "$(systemctl is-active docker)" != "active" ]; then \
|
||||||
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||||
sleep 5; sudo systemctl start docker; sleep 5;\
|
sleep 5; sudo systemctl start docker; sleep 5;\
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ conn shared
|
||||||
right=%any
|
right=%any
|
||||||
encapsulation=yes
|
encapsulation=yes
|
||||||
authby=secret
|
authby=secret
|
||||||
pfs=no
|
pfs=yes
|
||||||
rekey=no
|
rekey=no
|
||||||
keyingtries=5
|
keyingtries=5
|
||||||
dpddelay=30
|
dpddelay=30
|
||||||
dpdtimeout=120
|
dpdtimeout=120
|
||||||
dpdaction=clear
|
dpdaction=clear
|
||||||
ikev2=never
|
ikev2=never
|
||||||
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp1024,aes128-sha1;modp1024
|
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048
|
||||||
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
|
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
|
||||||
ikelifetime=24h
|
ikelifetime=24h
|
||||||
salifetime=24h
|
salifetime=24h
|
||||||
|
|
@ -244,9 +244,9 @@ conn ikev2-cp
|
||||||
auto=add
|
auto=add
|
||||||
ikev2=insist
|
ikev2=insist
|
||||||
rekey=no
|
rekey=no
|
||||||
pfs=no
|
pfs=yes
|
||||||
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1
|
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048
|
||||||
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2
|
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
|
||||||
ikelifetime=24h
|
ikelifetime=24h
|
||||||
salifetime=24h
|
salifetime=24h
|
||||||
encapsulation=yes
|
encapsulation=yes
|
||||||
|
|
|
||||||
|
|
@ -548,3 +548,13 @@ void Settings::toggleDevGatewayEnv(bool enabled)
|
||||||
{
|
{
|
||||||
m_isDevGatewayEnv = enabled;
|
m_isDevGatewayEnv = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Settings::isHomeAdLabelVisible()
|
||||||
|
{
|
||||||
|
return value("Conf/homeAdLabelVisible", true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::disableHomeAdLabel()
|
||||||
|
{
|
||||||
|
setValue("Conf/homeAdLabelVisible", false);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,9 @@ public:
|
||||||
bool isDevGatewayEnv();
|
bool isDevGatewayEnv();
|
||||||
void toggleDevGatewayEnv(bool enabled);
|
void toggleDevGatewayEnv(bool enabled);
|
||||||
|
|
||||||
|
bool isHomeAdLabelVisible();
|
||||||
|
void disableHomeAdLabel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void saveLogsChanged(bool enabled);
|
void saveLogsChanged(bool enabled);
|
||||||
void screenshotsEnabledChanged(bool enabled);
|
void screenshotsEnabledChanged(bool enabled);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -10,14 +10,13 @@ FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
|
||||||
m_focusChain {},
|
m_focusChain {},
|
||||||
m_focusedItem { nullptr },
|
m_focusedItem { nullptr },
|
||||||
m_rootObjects {},
|
m_rootObjects {},
|
||||||
m_defaultFocusItem { QSharedPointer<QQuickItem>() },
|
m_defaultFocusItem { nullptr },
|
||||||
m_lvfc { nullptr }
|
m_lvfc { nullptr }
|
||||||
{
|
{
|
||||||
QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this,
|
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url) {
|
||||||
[this](QObject *object, const QUrl &url) {
|
|
||||||
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
|
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
|
||||||
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
|
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
|
||||||
m_defaultFocusItem.reset(newDefaultFocusItem);
|
m_defaultFocusItem = newDefaultFocusItem;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -65,7 +64,7 @@ void FocusController::setFocusItem(QQuickItem *item)
|
||||||
|
|
||||||
void FocusController::setFocusOnDefaultItem()
|
void FocusController::setFocusOnDefaultItem()
|
||||||
{
|
{
|
||||||
setFocusItem(m_defaultFocusItem.get());
|
setFocusItem(m_defaultFocusItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FocusController::pushRootObject(QObject *object)
|
void FocusController::pushRootObject(QObject *object)
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ private:
|
||||||
void focusPreviousListViewItem();
|
void focusPreviousListViewItem();
|
||||||
void dropListView();
|
void dropListView();
|
||||||
|
|
||||||
QSharedPointer<QQmlApplicationEngine> m_engine; // Pointer to engine to get root object
|
QQmlApplicationEngine *m_engine; // Pointer to engine to get root object
|
||||||
QList<QObject *> m_focusChain; // List of current objects to be focused
|
QList<QObject *> m_focusChain; // List of current objects to be focused
|
||||||
QQuickItem *m_focusedItem; // Pointer to the active focus item
|
QQuickItem *m_focusedItem; // Pointer to the active focus item
|
||||||
QStack<QObject *> m_rootObjects;
|
QStack<QObject *> m_rootObjects; // Pointer to stack of roots for focus chain
|
||||||
QSharedPointer<QQuickItem> m_defaultFocusItem;
|
QQuickItem *m_defaultFocusItem;
|
||||||
|
|
||||||
ListViewFocusController *m_lvfc; // ListView focus manager
|
ListViewFocusController *m_lvfc; // ListView focus manager
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -333,3 +333,14 @@ bool SettingsController::isOnTv()
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SettingsController::isHomeAdLabelVisible()
|
||||||
|
{
|
||||||
|
return m_settings->isHomeAdLabelVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsController::disableHomeAdLabel()
|
||||||
|
{
|
||||||
|
m_settings->disableHomeAdLabel();
|
||||||
|
emit isHomeAdLabelVisibleChanged(false);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ public:
|
||||||
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
|
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
|
||||||
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)
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleAmneziaDns(bool enable);
|
void toggleAmneziaDns(bool enable);
|
||||||
bool isAmneziaDnsEnabled();
|
bool isAmneziaDnsEnabled();
|
||||||
|
|
@ -94,6 +96,9 @@ public slots:
|
||||||
|
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
|
|
||||||
|
bool isHomeAdLabelVisible();
|
||||||
|
void disableHomeAdLabel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void primaryDnsChanged();
|
void primaryDnsChanged();
|
||||||
void secondaryDnsChanged();
|
void secondaryDnsChanged();
|
||||||
|
|
@ -119,6 +124,8 @@ signals:
|
||||||
void gatewayEndpointChanged(const QString &endpoint);
|
void gatewayEndpointChanged(const QString &endpoint);
|
||||||
void devGatewayEnvChanged(bool enabled);
|
void devGatewayEnvChanged(bool enabled);
|
||||||
|
|
||||||
|
void isHomeAdLabelVisibleChanged(bool visible);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
|
|
|
||||||
|
|
@ -65,28 +65,29 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
auto speed = apiServiceData.serviceInfo.speed;
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
|
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||||
"Works for any sites. Speed up to %1 MBit/s")
|
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.")
|
||||||
.arg(speed);
|
.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree){
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
|
QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
if (isServiceAvailable) {
|
if (!isServiceAvailable) {
|
||||||
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
|
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
|
||||||
|
"return to the previous screen, and try again.</a>");
|
||||||
}
|
}
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return tr("Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. "
|
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
|
||||||
"It works for all websites, even in countries with the highest level of internet censorship.");
|
"Works for any sites with no restrictions.");
|
||||||
} else {
|
} else {
|
||||||
return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship");
|
return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case IsServiceAvailableRole: {
|
case IsServiceAvailableRole: {
|
||||||
if (serviceType == serviceType::amneziaFree) {
|
if (serviceType == serviceType::amneziaFree) {
|
||||||
if (isServiceAvailable) {
|
if (!isServiceAvailable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +144,8 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
|
||||||
m_selectedServiceIndex = 0;
|
m_selectedServiceIndex = 0;
|
||||||
} else {
|
} else {
|
||||||
for (const auto &service : services) {
|
for (const auto &service : services) {
|
||||||
m_services.push_back(getApiServicesData(service.toObject()));
|
auto serviceObject = service.toObject();
|
||||||
|
m_services.push_back(getApiServicesData(serviceObject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,9 +247,9 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
||||||
serviceData.type = serviceType;
|
serviceData.type = serviceType;
|
||||||
serviceData.protocol = serviceProtocol;
|
serviceData.protocol = serviceProtocol;
|
||||||
|
|
||||||
serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();
|
serviceData.storeEndpoint = data.value(configKey::storeEndpoint).toString();
|
||||||
|
|
||||||
if (serviceInfo.value(configKey::isAvailable).isBool()) {
|
if (data.value(configKey::isAvailable).isBool()) {
|
||||||
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
|
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
|
||||||
} else {
|
} else {
|
||||||
serviceData.isServiceAvailable = true;
|
serviceData.isServiceAvailable = true;
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ QString LanguageModel::getCurrentSiteUrl()
|
||||||
{
|
{
|
||||||
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
|
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/kldscp/amnezia.org";
|
case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/amnezia/amnezia.org";
|
||||||
default: return "https://amnezia.org";
|
default: return "https://amnezia.org";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break;
|
||||||
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
|
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
|
||||||
|
|
||||||
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
|
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
|
||||||
|
|
@ -58,6 +59,7 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString();
|
||||||
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
|
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
|
||||||
|
|
||||||
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
|
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
|
||||||
|
|
@ -92,6 +94,7 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
|
||||||
m_serverProtocolConfig.insert(config_key::transport_proto,
|
m_serverProtocolConfig.insert(config_key::transport_proto,
|
||||||
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
|
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
|
||||||
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
|
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
|
||||||
|
m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
|
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
|
||||||
m_serverProtocolConfig[config_key::junkPacketCount] =
|
m_serverProtocolConfig[config_key::junkPacketCount] =
|
||||||
serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
|
serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
|
||||||
|
|
@ -168,6 +171,7 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
|
roles[SubnetAddressRole] = "subnetAddress";
|
||||||
roles[PortRole] = "port";
|
roles[PortRole] = "port";
|
||||||
|
|
||||||
roles[ClientMtuRole] = "clientMtu";
|
roles[ClientMtuRole] = "clientMtu";
|
||||||
|
|
@ -197,6 +201,7 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
|
||||||
clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
|
clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
|
||||||
clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
|
clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
|
||||||
|
|
||||||
|
subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
|
port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
|
||||||
serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
|
serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
|
||||||
serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
|
serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
|
||||||
|
|
@ -216,7 +221,7 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
|
||||||
|
|
||||||
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
|
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
|
||||||
{
|
{
|
||||||
if (port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
|
if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
|
||||||
|| serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
|
|| serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
|
||||||
|| serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
|
|| serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
|
||||||
|| serverInitPacketMagicHeader != other.serverInitPacketMagicHeader
|
|| serverInitPacketMagicHeader != other.serverInitPacketMagicHeader
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ struct AwgConfig
|
||||||
{
|
{
|
||||||
AwgConfig(const QJsonObject &jsonConfig);
|
AwgConfig(const QJsonObject &jsonConfig);
|
||||||
|
|
||||||
|
QString subnetAddress;
|
||||||
QString port;
|
QString port;
|
||||||
|
|
||||||
QString clientMtu;
|
QString clientMtu;
|
||||||
|
|
@ -43,7 +44,8 @@ class AwgConfigModel : public QAbstractListModel
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
PortRole = Qt::UserRole + 1,
|
SubnetAddressRole = Qt::UserRole + 1,
|
||||||
|
PortRole,
|
||||||
|
|
||||||
ClientMtuRole,
|
ClientMtuRole,
|
||||||
ClientJunkPacketCountRole,
|
ClientJunkPacketCountRole,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case Roles::SubnetAddressRole: m_serverProtocolConfig.insert(config_key::subnet_address, value.toString()); break;
|
||||||
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
|
case Roles::PortRole: m_serverProtocolConfig.insert(config_key::port, value.toString()); break;
|
||||||
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
|
case Roles::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +37,7 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case Roles::SubnetAddressRole: return m_serverProtocolConfig.value(config_key::subnet_address).toString();
|
||||||
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
|
case Roles::PortRole: return m_serverProtocolConfig.value(config_key::port).toString();
|
||||||
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
|
case Roles::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +58,7 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
|
||||||
m_serverProtocolConfig.insert(config_key::transport_proto,
|
m_serverProtocolConfig.insert(config_key::transport_proto,
|
||||||
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
|
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
|
||||||
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
|
m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config);
|
||||||
|
m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
|
m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
|
||||||
|
|
||||||
auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
|
auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
|
||||||
|
|
@ -96,6 +99,7 @@ QHash<int, QByteArray> WireGuardConfigModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
|
roles[SubnetAddressRole] = "subnetAddress";
|
||||||
roles[PortRole] = "port";
|
roles[PortRole] = "port";
|
||||||
roles[ClientMtuRole] = "clientMtu";
|
roles[ClientMtuRole] = "clientMtu";
|
||||||
|
|
||||||
|
|
@ -108,12 +112,13 @@ WgConfig::WgConfig(const QJsonObject &serverProtocolConfig)
|
||||||
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
|
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
|
||||||
clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu);
|
clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::wireguard::defaultMtu);
|
||||||
|
|
||||||
|
subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
|
port = serverProtocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
|
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
|
||||||
{
|
{
|
||||||
if (port != other.port) {
|
if (subnetAddress != other.subnetAddress || port != other.port) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ struct WgConfig
|
||||||
{
|
{
|
||||||
WgConfig(const QJsonObject &jsonConfig);
|
WgConfig(const QJsonObject &jsonConfig);
|
||||||
|
|
||||||
|
QString subnetAddress;
|
||||||
QString port;
|
QString port;
|
||||||
QString clientMtu;
|
QString clientMtu;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,8 @@ class WireGuardConfigModel : public QAbstractListModel
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
PortRole = Qt::UserRole + 1,
|
SubnetAddressRole = Qt::UserRole + 1,
|
||||||
|
PortRole,
|
||||||
ClientMtuRole
|
ClientMtuRole
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
73
client/ui/qml/Components/AdLabel.qml
Normal file
73
client/ui/qml/Components/AdLabel.qml
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
|
import Style 1.0
|
||||||
|
|
||||||
|
import "../Config"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real contentHeight: ad.implicitHeight + ad.anchors.topMargin + ad.anchors.bottomMargin
|
||||||
|
|
||||||
|
border.width: 1
|
||||||
|
border.color: AmneziaStyle.color.goldenApricot
|
||||||
|
color: AmneziaStyle.color.transparent
|
||||||
|
radius: 13
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
// visible: GC.isDesktop() && ServersModel.isDefaultServerFromApi
|
||||||
|
// && ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && SettingsController.isHomeAdLabelVisible
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: function() {
|
||||||
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/premium")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: ad
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
|
||||||
|
Image {
|
||||||
|
source: "qrc:/images/controls/amnezia.svg"
|
||||||
|
sourceSize: Qt.size(36, 36)
|
||||||
|
|
||||||
|
layer {
|
||||||
|
effect: ColorOverlay {
|
||||||
|
color: AmneziaStyle.color.paleGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptionTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
|
||||||
|
text: qsTr("Amnezia Premium - for access to any website")
|
||||||
|
color: AmneziaStyle.color.pearlGray
|
||||||
|
|
||||||
|
lineHeight: 18
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageButtonType {
|
||||||
|
image: "qrc:/images/controls/close.svg"
|
||||||
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
onClicked: function() {
|
||||||
|
SettingsController.disableHomeAdLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -135,7 +135,7 @@ DrawerType2 {
|
||||||
|
|
||||||
backgroundColor: AmneziaStyle.color.slateGray
|
backgroundColor: AmneziaStyle.color.slateGray
|
||||||
|
|
||||||
textFieldPlaceholderText: qsTr("application name")
|
textField.placeholderText: qsTr("application name")
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Item {
|
||||||
readonly property string drawerExpandedStateName: "expanded"
|
readonly property string drawerExpandedStateName: "expanded"
|
||||||
readonly property string drawerCollapsedStateName: "collapsed"
|
readonly property string drawerCollapsedStateName: "collapsed"
|
||||||
|
|
||||||
readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive && (dragArea.drag.active === true))
|
readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive() && (dragArea.drag.active === true))
|
||||||
readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
|
readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
|
||||||
|
|
||||||
property Component collapsedStateContent
|
property Component collapsedStateContent
|
||||||
|
|
@ -123,7 +123,7 @@ Item {
|
||||||
id: background
|
id: background
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
|
color: root.isCollapsedStateActive() ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
PropertyAnimation { duration: 200 }
|
PropertyAnimation { duration: 200 }
|
||||||
|
|
|
||||||
|
|
@ -216,9 +216,7 @@ Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: header
|
id: header
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.fill: parent
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: 16
|
anchors.topMargin: 16
|
||||||
|
|
||||||
BackButtonType {
|
BackButtonType {
|
||||||
|
|
@ -226,31 +224,21 @@ Item {
|
||||||
backButtonImage: root.headerBackButtonImage
|
backButtonImage: root.headerBackButtonImage
|
||||||
backButtonFunction: function() { menu.closeTriggered() }
|
backButtonFunction: function() { menu.closeTriggered() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: col
|
|
||||||
anchors.top: header.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: 16
|
|
||||||
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Header2Type {
|
Header2Type {
|
||||||
anchors.left: parent.left
|
Layout.leftMargin: 16
|
||||||
anchors.right: parent.right
|
Layout.rightMargin: 16
|
||||||
anchors.leftMargin: 16
|
Layout.bottomMargin: 16
|
||||||
anchors.rightMargin: 16
|
Layout.fillWidth: true
|
||||||
|
|
||||||
headerText: root.headerText
|
headerText: root.headerText
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: listViewLoader
|
id: listViewLoader
|
||||||
sourceComponent: root.listView
|
sourceComponent: root.listView
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ ListView {
|
||||||
|
|
||||||
property bool isFocusable: true
|
property bool isFocusable: true
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBarType {}
|
||||||
|
|
||||||
ButtonGroup {
|
ButtonGroup {
|
||||||
id: buttonGroup
|
id: buttonGroup
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ Item {
|
||||||
id: timer
|
id: timer
|
||||||
interval: 200 // Milliseconds
|
interval: 200 // Milliseconds
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
console.debug(">>> PageType timer triggered")
|
|
||||||
FocusController.resetRootObject()
|
FocusController.resetRootObject()
|
||||||
FocusController.setFocusOnDefaultItem()
|
FocusController.setFocusOnDefaultItem()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,9 @@ Item {
|
||||||
property var clickedFunc
|
property var clickedFunc
|
||||||
|
|
||||||
property alias textField: textField
|
property alias textField: textField
|
||||||
property alias textFieldText: textField.text
|
|
||||||
property string textFieldTextColor: AmneziaStyle.color.paleGray
|
property string textFieldTextColor: AmneziaStyle.color.paleGray
|
||||||
property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray
|
property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray
|
||||||
|
|
||||||
property string textFieldPlaceholderText
|
|
||||||
property bool textFieldEditable: true
|
property bool textFieldEditable: true
|
||||||
|
|
||||||
property string borderColor: AmneziaStyle.color.slateGray
|
property string borderColor: AmneziaStyle.color.slateGray
|
||||||
|
|
@ -101,7 +99,6 @@ Item {
|
||||||
|
|
||||||
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
|
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
|
||||||
|
|
||||||
placeholderText: root.textFieldPlaceholderText
|
|
||||||
placeholderTextColor: AmneziaStyle.color.charcoalGray
|
placeholderTextColor: AmneziaStyle.color.charcoalGray
|
||||||
|
|
||||||
selectionColor: AmneziaStyle.color.richBrown
|
selectionColor: AmneziaStyle.color.richBrown
|
||||||
|
|
@ -129,8 +126,8 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (checkEmptyText && textFieldText === "") {
|
if (root.checkEmptyText && text === "") {
|
||||||
errorText = qsTr("The field can't be empty")
|
root.errorText = qsTr("The field can't be empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,6 @@ QtObject {
|
||||||
readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
|
readonly property color softGoldenApricot: Qt.rgba(251/255, 178/255, 106/255, 0.3)
|
||||||
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
readonly property color mistyGray: Qt.rgba(215/255, 216/255, 219/255, 0.8)
|
||||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,18 +66,18 @@ PageType {
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Gateway endpoint")
|
headerText: qsTr("Gateway endpoint")
|
||||||
textFieldText: SettingsController.gatewayEndpoint
|
textField.text: SettingsController.gatewayEndpoint
|
||||||
|
|
||||||
buttonImageSource: textFieldText !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
|
buttonImageSource: textField.text !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
SettingsController.resetGatewayEndpoint()
|
SettingsController.resetGatewayEndpoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
if (textFieldText !== SettingsController.gatewayEndpoint) {
|
if (textField.text !== SettingsController.gatewayEndpoint) {
|
||||||
SettingsController.gatewayEndpoint = textFieldText
|
SettingsController.gatewayEndpoint = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
|
@ -42,8 +43,18 @@ PageType {
|
||||||
objectName: "homeColumnLayout"
|
objectName: "homeColumnLayout"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 34
|
anchors.topMargin: 12
|
||||||
anchors.bottomMargin: 34
|
anchors.bottomMargin: 16
|
||||||
|
|
||||||
|
AdLabel {
|
||||||
|
id: adLabel
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: adLabel.contentHeight
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 22
|
||||||
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: loggingButton
|
id: loggingButton
|
||||||
|
|
@ -86,7 +97,6 @@ PageType {
|
||||||
objectName: "splitTunnelingButton"
|
objectName: "splitTunnelingButton"
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||||
Layout.bottomMargin: 34
|
|
||||||
leftPadding: 16
|
leftPadding: 16
|
||||||
rightPadding: 16
|
rightPadding: 16
|
||||||
|
|
||||||
|
|
@ -256,11 +266,11 @@ PageType {
|
||||||
objectName: "rowLayoutLabel"
|
objectName: "rowLayoutLabel"
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
|
Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
|
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive
|
||||||
hoverEnabled: enabled
|
hoverEnabled: enabled
|
||||||
|
|
||||||
implicitHeight: 36
|
implicitHeight: 36
|
||||||
|
|
@ -278,8 +288,9 @@ PageType {
|
||||||
buttonTextLabel.font.pixelSize: 13
|
buttonTextLabel.font.pixelSize: 13
|
||||||
buttonTextLabel.font.weight: 400
|
buttonTextLabel.font.weight: 400
|
||||||
|
|
||||||
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
|
text: drawer.isCollapsedStateActive ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
|
||||||
leftImageSource: ServersModel.defaultServerImagePathCollapsed
|
leftImageSource: ServersModel.defaultServerImagePathCollapsed
|
||||||
|
leftImageColor: ""
|
||||||
changeLeftImageSize: false
|
changeLeftImageSize: false
|
||||||
|
|
||||||
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
|
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
|
||||||
|
|
@ -337,7 +348,6 @@ PageType {
|
||||||
objectName: "containersListView"
|
objectName: "containersListView"
|
||||||
|
|
||||||
rootWidth: root.width
|
rootWidth: root.width
|
||||||
height: 500 // TODO: make calculated
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
objectName: "rowLayoutConnections"
|
objectName: "rowLayoutConnections"
|
||||||
|
|
|
||||||
|
|
@ -103,12 +103,12 @@ PageType {
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
|
||||||
headerText: qsTr("MTU")
|
headerText: qsTr("MTU")
|
||||||
textFieldText: clientMtu
|
textField.text: clientMtu
|
||||||
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== clientMtu) {
|
if (textField.text !== clientMtu) {
|
||||||
clientMtu = textFieldText
|
clientMtu = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
|
|
@ -121,12 +121,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: "Jc - Junk packet count"
|
headerText: "Jc - Junk packet count"
|
||||||
textFieldText: clientJunkPacketCount
|
textField.text: clientJunkPacketCount
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== clientJunkPacketCount) {
|
if (textField.text !== clientJunkPacketCount) {
|
||||||
clientJunkPacketCount = textFieldText
|
clientJunkPacketCount = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,12 +141,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: "Jmin - Junk packet minimum size"
|
headerText: "Jmin - Junk packet minimum size"
|
||||||
textFieldText: clientJunkPacketMinSize
|
textField.text: clientJunkPacketMinSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== clientJunkPacketMinSize) {
|
if (textField.text !== clientJunkPacketMinSize) {
|
||||||
clientJunkPacketMinSize = textFieldText
|
clientJunkPacketMinSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,12 +161,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: "Jmax - Junk packet maximum size"
|
headerText: "Jmax - Junk packet maximum size"
|
||||||
textFieldText: clientJunkPacketMaxSize
|
textField.text: clientJunkPacketMaxSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== clientJunkPacketMaxSize) {
|
if (textField.text !== clientJunkPacketMaxSize) {
|
||||||
clientJunkPacketMaxSize = textFieldText
|
clientJunkPacketMaxSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +189,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -200,7 +200,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "S1 - Init packet junk size"
|
headerText: "S1 - Init packet junk size"
|
||||||
textFieldText: serverInitPacketJunkSize
|
textField.text: serverInitPacketJunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -211,7 +211,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "S2 - Response packet junk size"
|
headerText: "S2 - Response packet junk size"
|
||||||
textFieldText: serverResponsePacketJunkSize
|
textField.text: serverResponsePacketJunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -222,7 +222,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "H1 - Init packet magic header"
|
headerText: "H1 - Init packet magic header"
|
||||||
textFieldText: serverInitPacketMagicHeader
|
textField.text: serverInitPacketMagicHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -233,7 +233,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "H2 - Response packet magic header"
|
headerText: "H2 - Response packet magic header"
|
||||||
textFieldText: serverResponsePacketMagicHeader
|
textField.text: serverResponsePacketMagicHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -244,7 +244,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "H3 - Underload packet magic header"
|
headerText: "H3 - Underload packet magic header"
|
||||||
textFieldText: serverUnderloadPacketMagicHeader
|
textField.text: serverUnderloadPacketMagicHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
|
|
@ -255,7 +255,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: "H4 - Transport packet magic header"
|
headerText: "H4 - Transport packet magic header"
|
||||||
textFieldText: serverTransportPacketMagicHeader
|
textField.text: serverTransportPacketMagicHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,13 @@ PageType {
|
||||||
ListView {
|
ListView {
|
||||||
id: listview
|
id: listview
|
||||||
|
|
||||||
|
property bool isFocusable: true
|
||||||
|
|
||||||
anchors.top: backButtonLayout.bottom
|
anchors.top: backButtonLayout.bottom
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
property bool isFocusable: true
|
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
Keys.onTabPressed: {
|
||||||
FocusController.nextKeyTabItem()
|
FocusController.nextKeyTabItem()
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ PageType {
|
||||||
implicitWidth: listview.width
|
implicitWidth: listview.width
|
||||||
implicitHeight: col.implicitHeight
|
implicitHeight: col.implicitHeight
|
||||||
|
|
||||||
property alias portTextField: portTextField
|
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
|
||||||
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|
@ -98,20 +98,19 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: portTextField
|
id: vpnAddressSubnetTextField
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
enabled: delegateItem.isEnabled
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("VPN address subnet")
|
||||||
textFieldText: port
|
textField.text: subnetAddress
|
||||||
textField.maximumLength: 5
|
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== subnetAddress) {
|
||||||
port = textFieldText
|
subnetAddress = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,22 +118,23 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: mtuTextField
|
id: portTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("MTU")
|
enabled: delegateItem.isEnabled
|
||||||
textFieldText: mtu
|
|
||||||
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
headerText: qsTr("Port")
|
||||||
|
textField.text: port
|
||||||
|
textField.maximumLength: 5
|
||||||
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText === "") {
|
if (textField.text !== port) {
|
||||||
textFieldText = "0"
|
port = textField.text
|
||||||
}
|
|
||||||
if (textFieldText !== mtu) {
|
|
||||||
mtu = textFieldText
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,16 +144,16 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Jc - Junk packet count")
|
headerText: qsTr("Jc - Junk packet count")
|
||||||
textFieldText: serverJunkPacketCount
|
textField.text: serverJunkPacketCount
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText === "") {
|
if (textField.text === "") {
|
||||||
textFieldText = "0"
|
textField.text = "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textFieldText !== serverJunkPacketCount) {
|
if (textField.text !== serverJunkPacketCount) {
|
||||||
serverJunkPacketCount = textFieldText
|
serverJunkPacketCount = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,12 +166,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Jmin - Junk packet minimum size")
|
headerText: qsTr("Jmin - Junk packet minimum size")
|
||||||
textFieldText: serverJunkPacketMinSize
|
textField.text: serverJunkPacketMinSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverJunkPacketMinSize) {
|
if (textField.text !== serverJunkPacketMinSize) {
|
||||||
serverJunkPacketMinSize = textFieldText
|
serverJunkPacketMinSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,12 +184,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Jmax - Junk packet maximum size")
|
headerText: qsTr("Jmax - Junk packet maximum size")
|
||||||
textFieldText: serverJunkPacketMaxSize
|
textField.text: serverJunkPacketMaxSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverJunkPacketMaxSize) {
|
if (textField.text !== serverJunkPacketMaxSize) {
|
||||||
serverJunkPacketMaxSize = textFieldText
|
serverJunkPacketMaxSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,12 +202,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("S1 - Init packet junk size")
|
headerText: qsTr("S1 - Init packet junk size")
|
||||||
textFieldText: serverInitPacketJunkSize
|
textField.text: serverInitPacketJunkSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverInitPacketJunkSize) {
|
if (textField.text !== serverInitPacketJunkSize) {
|
||||||
serverInitPacketJunkSize = textFieldText
|
serverInitPacketJunkSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,12 +226,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("S2 - Response packet junk size")
|
headerText: qsTr("S2 - Response packet junk size")
|
||||||
textFieldText: serverResponsePacketJunkSize
|
textField.text: serverResponsePacketJunkSize
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverResponsePacketJunkSize) {
|
if (textField.text !== serverResponsePacketJunkSize) {
|
||||||
serverResponsePacketJunkSize = textFieldText
|
serverResponsePacketJunkSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,12 +250,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("H1 - Init packet magic header")
|
headerText: qsTr("H1 - Init packet magic header")
|
||||||
textFieldText: serverInitPacketMagicHeader
|
textField.text: serverInitPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverInitPacketMagicHeader) {
|
if (textField.text !== serverInitPacketMagicHeader) {
|
||||||
serverInitPacketMagicHeader = textFieldText
|
serverInitPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,12 +268,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("H2 - Response packet magic header")
|
headerText: qsTr("H2 - Response packet magic header")
|
||||||
textFieldText: serverResponsePacketMagicHeader
|
textField.text: serverResponsePacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverResponsePacketMagicHeader) {
|
if (textField.text !== serverResponsePacketMagicHeader) {
|
||||||
serverResponsePacketMagicHeader = textFieldText
|
serverResponsePacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,12 +286,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("H4 - Transport packet magic header")
|
headerText: qsTr("H4 - Transport packet magic header")
|
||||||
textFieldText: serverTransportPacketMagicHeader
|
textField.text: serverTransportPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverTransportPacketMagicHeader) {
|
if (textField.text !== serverTransportPacketMagicHeader) {
|
||||||
serverTransportPacketMagicHeader = textFieldText
|
serverTransportPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,12 +304,12 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("H3 - Underload packet magic header")
|
headerText: qsTr("H3 - Underload packet magic header")
|
||||||
textFieldText: serverUnderloadPacketMagicHeader
|
textField.text: serverUnderloadPacketMagicHeader
|
||||||
textField.validator: IntValidator { bottom: 0 }
|
textField.validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== serverUnderloadPacketMagicHeader) {
|
if (textField.text !== serverUnderloadPacketMagicHeader) {
|
||||||
serverUnderloadPacketMagicHeader = textFieldText
|
serverUnderloadPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,7 +332,8 @@ PageType {
|
||||||
junkPacketMaxSizeTextField.errorText === "" &&
|
junkPacketMaxSizeTextField.errorText === "" &&
|
||||||
junkPacketMinSizeTextField.errorText === "" &&
|
junkPacketMinSizeTextField.errorText === "" &&
|
||||||
junkPacketCountTextField.errorText === "" &&
|
junkPacketCountTextField.errorText === "" &&
|
||||||
portTextField.errorText === ""
|
portTextField.errorText === "" &&
|
||||||
|
vpnAddressSubnetTextField.errorText === ""
|
||||||
|
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,18 +89,18 @@ PageType {
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: 32
|
||||||
|
|
||||||
headerText: qsTr("Disguised as traffic from")
|
headerText: qsTr("Disguised as traffic from")
|
||||||
textFieldText: site
|
textField.text: site
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== site) {
|
if (textField.text !== site) {
|
||||||
var tmpText = textFieldText
|
var tmpText = textField.text
|
||||||
tmpText = tmpText.toLocaleLowerCase()
|
tmpText = tmpText.toLocaleLowerCase()
|
||||||
|
|
||||||
var indexHttps = tmpText.indexOf("https://")
|
var indexHttps = tmpText.indexOf("https://")
|
||||||
if (indexHttps === 0) {
|
if (indexHttps === 0) {
|
||||||
tmpText = textFieldText.substring(8)
|
tmpText = textField.text.substring(8)
|
||||||
} else {
|
} else {
|
||||||
site = textFieldText
|
site = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,13 +113,13 @@ PageType {
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== port) {
|
||||||
port = textFieldText
|
port = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,13 +88,13 @@ PageType {
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: 32
|
||||||
|
|
||||||
headerText: qsTr("VPN address subnet")
|
headerText: qsTr("VPN address subnet")
|
||||||
textFieldText: subnetAddress
|
textField.text: subnetAddress
|
||||||
|
|
||||||
parentFlickable: fl
|
parentFlickable: fl
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== subnetAddress) {
|
if (textField.text !== subnetAddress) {
|
||||||
subnetAddress = textFieldText
|
subnetAddress = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,13 +137,13 @@ PageType {
|
||||||
enabled: isPortEditable
|
enabled: isPortEditable
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== port) {
|
||||||
port = textFieldText
|
port = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +176,6 @@ PageType {
|
||||||
headerText: qsTr("Hash")
|
headerText: qsTr("Hash")
|
||||||
|
|
||||||
drawerParent: root
|
drawerParent: root
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
listView: ListViewWithRadioButtonType {
|
||||||
id: hashListView
|
id: hashListView
|
||||||
|
|
@ -225,7 +224,6 @@ PageType {
|
||||||
headerText: qsTr("Cipher")
|
headerText: qsTr("Cipher")
|
||||||
|
|
||||||
drawerParent: root
|
drawerParent: root
|
||||||
parentFlickable: fl
|
|
||||||
|
|
||||||
listView: ListViewWithRadioButtonType {
|
listView: ListViewWithRadioButtonType {
|
||||||
id: cipherListView
|
id: cipherListView
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,13 @@ PageType {
|
||||||
enabled: isPortEditable
|
enabled: isPortEditable
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== port) {
|
||||||
port = textFieldText
|
port = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ import "../Components"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: focusItem
|
id: focusItem
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
|
|
@ -99,12 +97,12 @@ PageType {
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
|
||||||
headerText: qsTr("MTU")
|
headerText: qsTr("MTU")
|
||||||
textFieldText: clientMtu
|
textField.text: clientMtu
|
||||||
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== clientMtu) {
|
if (textField.text !== clientMtu) {
|
||||||
clientMtu = textFieldText
|
clientMtu = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
|
|
@ -126,7 +124,7 @@ PageType {
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ PageType {
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: delegateItem
|
id: delegateItem
|
||||||
|
|
||||||
property alias focusItemId: portTextField.textField
|
property alias focusItemId: vpnAddressSubnetTextField
|
||||||
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||||
|
|
||||||
implicitWidth: listview.width
|
implicitWidth: listview.width
|
||||||
|
|
@ -83,20 +83,18 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: portTextField
|
id: vpnAddressSubnetTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 40
|
Layout.topMargin: 40
|
||||||
|
|
||||||
enabled: delegateItem.isEnabled
|
enabled: delegateItem.isEnabled
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("VPN address subnet")
|
||||||
textFieldText: port
|
textField.text: subnetAddress
|
||||||
textField.maximumLength: 5
|
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== subnetAddress) {
|
||||||
port = textFieldText
|
subnetAddress = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,22 +102,23 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: mtuTextField
|
id: portTextField
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
|
||||||
headerText: qsTr("MTU")
|
enabled: delegateItem.isEnabled
|
||||||
textFieldText: mtu
|
|
||||||
textField.validator: IntValidator { bottom: 576; top: 65535 }
|
headerText: qsTr("Port")
|
||||||
|
textField.text: port
|
||||||
|
textField.maximumLength: 5
|
||||||
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText === "") {
|
if (textField.text !== port) {
|
||||||
textFieldText = "0"
|
port = textField.text
|
||||||
}
|
|
||||||
if (textFieldText !== mtu) {
|
|
||||||
mtu = textFieldText
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +128,8 @@ PageType {
|
||||||
Layout.topMargin: 24
|
Layout.topMargin: 24
|
||||||
Layout.bottomMargin: 24
|
Layout.bottomMargin: 24
|
||||||
|
|
||||||
enabled: portTextField.errorText === ""
|
enabled: portTextField.errorText === "" &&
|
||||||
|
vpnAddressSubnetTextField.errorText === ""
|
||||||
|
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,18 +86,18 @@ PageType {
|
||||||
Layout.topMargin: 32
|
Layout.topMargin: 32
|
||||||
|
|
||||||
headerText: qsTr("Disguised as traffic from")
|
headerText: qsTr("Disguised as traffic from")
|
||||||
textFieldText: site
|
textField.text: site
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
if (textFieldText !== site) {
|
if (textField.text !== site) {
|
||||||
var tmpText = textFieldText
|
var tmpText = textField.text
|
||||||
tmpText = tmpText.toLocaleLowerCase()
|
tmpText = tmpText.toLocaleLowerCase()
|
||||||
|
|
||||||
var indexHttps = tmpText.indexOf("https://")
|
var indexHttps = tmpText.indexOf("https://")
|
||||||
if (indexHttps === 0) {
|
if (indexHttps === 0) {
|
||||||
tmpText = textFieldText.substring(8)
|
tmpText = textField.text.substring(8)
|
||||||
} else {
|
} else {
|
||||||
site = textFieldText
|
site = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -211,9 +211,9 @@ PageType {
|
||||||
port = tempPort
|
port = tempPort
|
||||||
username = tempUsername
|
username = tempUsername
|
||||||
password = tempPassword
|
password = tempPassword
|
||||||
portTextField.textFieldText = port
|
portTextField.textField.text = port
|
||||||
usernameTextField.textFieldText = username
|
usernameTextField.textField.text = username
|
||||||
passwordTextField.textFieldText = password
|
passwordTextField.textField.text = password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,14 +231,14 @@ PageType {
|
||||||
parentFlickable: fl
|
parentFlickable: fl
|
||||||
|
|
||||||
headerText: qsTr("Port")
|
headerText: qsTr("Port")
|
||||||
textFieldText: port
|
textField.text: port
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
if (textFieldText !== port) {
|
if (textField.text !== port) {
|
||||||
port = textFieldText
|
port = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,14 +251,14 @@ PageType {
|
||||||
parentFlickable: fl
|
parentFlickable: fl
|
||||||
|
|
||||||
headerText: qsTr("Username")
|
headerText: qsTr("Username")
|
||||||
textFieldPlaceholderText: "username"
|
textField.placeholderText: "username"
|
||||||
textFieldText: username
|
textField.text: username
|
||||||
textField.maximumLength: 32
|
textField.maximumLength: 32
|
||||||
|
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
if (textFieldText !== username) {
|
if (textField.text !== username) {
|
||||||
username = textFieldText
|
username = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -273,12 +273,12 @@ PageType {
|
||||||
parentFlickable: fl
|
parentFlickable: fl
|
||||||
|
|
||||||
headerText: qsTr("Password")
|
headerText: qsTr("Password")
|
||||||
textFieldPlaceholderText: "password"
|
textField.placeholderText: "password"
|
||||||
textFieldText: password
|
textField.text: password
|
||||||
textField.maximumLength: 32
|
textField.maximumLength: 32
|
||||||
|
|
||||||
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
|
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
|
||||||
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
|
buttonImageSource: textField.text !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
|
||||||
: ""
|
: ""
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
|
|
@ -286,9 +286,9 @@ PageType {
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onFocusChanged: {
|
textField.onFocusChanged: {
|
||||||
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
if (textFieldText !== password) {
|
if (textField.text !== password) {
|
||||||
password = textFieldText
|
password = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,19 +309,19 @@ PageType {
|
||||||
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
|
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (usernameTextField.textFieldText && passwordTextField.textFieldText === "") {
|
if (usernameTextField.textField.text && passwordTextField.textField.text === "") {
|
||||||
passwordTextField.errorText = qsTr("Password cannot be empty")
|
passwordTextField.errorText = qsTr("Password cannot be empty")
|
||||||
return
|
return
|
||||||
} else if (usernameTextField.textFieldText === "" && passwordTextField.textFieldText) {
|
} else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) {
|
||||||
usernameTextField.errorText = qsTr("Username cannot be empty")
|
usernameTextField.errorText = qsTr("Username cannot be empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||||
InstallController.updateContainer(Socks5ProxyConfigModel.getConfig())
|
InstallController.updateContainer(Socks5ProxyConfigModel.getConfig())
|
||||||
tempPort = portTextField.textFieldText
|
tempPort = portTextField.textField.text
|
||||||
tempUsername = usernameTextField.textFieldText
|
tempUsername = usernameTextField.textField.text
|
||||||
tempPassword = passwordTextField.textFieldText
|
tempPassword = passwordTextField.textField.text
|
||||||
changeSettingsDrawer.closeTriggered()
|
changeSettingsDrawer.closeTriggered()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,6 @@ PageType {
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: backup
|
id: backup
|
||||||
visible: !SettingsController.isOnTv()
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
text: qsTr("Backup")
|
text: qsTr("Backup")
|
||||||
|
|
@ -99,9 +98,7 @@ PageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DividerType {
|
DividerType {}
|
||||||
visible: !SettingsController.isOnTv()
|
|
||||||
}
|
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
id: about
|
id: about
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ PageType {
|
||||||
ListView {
|
ListView {
|
||||||
id: menuContent
|
id: menuContent
|
||||||
|
|
||||||
property var selectedText
|
property bool isFocusable: true
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: menuContent.contentItem.height
|
height: parent.height
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
interactive: false
|
interactive: true
|
||||||
model: ApiCountryModel
|
model: ApiCountryModel
|
||||||
|
|
||||||
ButtonGroup {
|
ButtonGroup {
|
||||||
|
|
@ -34,8 +34,8 @@ PageType {
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
|
|
||||||
implicitWidth: parent.width
|
width: menuContent.width
|
||||||
implicitHeight: content.implicitHeight
|
height: content.implicitHeight
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
VerticalRadioButton {
|
VerticalRadioButton {
|
||||||
|
|
|
||||||
|
|
@ -15,62 +15,101 @@ import "../Components"
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
FlickableType {
|
property list<QtObject> labelsModel: [
|
||||||
id: fl
|
regionObject,
|
||||||
anchors.top: parent.top
|
priceObject,
|
||||||
anchors.bottom: parent.bottom
|
endDateObject,
|
||||||
contentHeight: content.height
|
speedObject
|
||||||
|
]
|
||||||
|
|
||||||
ColumnLayout {
|
QtObject {
|
||||||
id: content
|
id: regionObject
|
||||||
|
|
||||||
anchors.top: parent.top
|
readonly property string title: qsTr("For the region")
|
||||||
anchors.left: parent.left
|
readonly property string contentKey: "region"
|
||||||
anchors.right: parent.right
|
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: priceObject
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Price")
|
||||||
|
readonly property string contentKey: "price"
|
||||||
|
readonly property string objectImageSource: "qrc:/images/controls/tag.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: endDateObject
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Valid until")
|
||||||
|
readonly property string contentKey: "endDate"
|
||||||
|
readonly property string objectImageSource: "qrc:/images/controls/history.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: speedObject
|
||||||
|
|
||||||
|
readonly property string title: qsTr("Speed")
|
||||||
|
readonly property string contentKey: "speed"
|
||||||
|
readonly property string objectImageSource: "qrc:/images/controls/gauge.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property bool isFocusable: true
|
||||||
|
|
||||||
|
Keys.onTabPressed: {
|
||||||
|
FocusController.nextKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onBacktabPressed: {
|
||||||
|
FocusController.previousKeyTabItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onUpPressed: {
|
||||||
|
FocusController.nextKeyUpItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDownPressed: {
|
||||||
|
FocusController.nextKeyDownItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onLeftPressed: {
|
||||||
|
FocusController.nextKeyLeftItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onRightPressed: {
|
||||||
|
FocusController.nextKeyRightItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBarType {}
|
||||||
|
|
||||||
|
model: labelsModel
|
||||||
|
clip: true
|
||||||
|
reuseItems: true
|
||||||
|
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
LabelWithImageType {
|
LabelWithImageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: 16
|
Layout.margins: 16
|
||||||
|
|
||||||
imageSource: "qrc:/images/controls/map-pin.svg"
|
imageSource: objectImageSource
|
||||||
leftText: qsTr("For the region")
|
leftText: title
|
||||||
rightText: ApiServicesModel.getSelectedServiceData("region")
|
rightText: ApiServicesModel.getSelectedServiceData(contentKey)
|
||||||
}
|
|
||||||
|
|
||||||
LabelWithImageType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 16
|
|
||||||
|
|
||||||
imageSource: "qrc:/images/controls/tag.svg"
|
|
||||||
leftText: qsTr("Price")
|
|
||||||
rightText: ApiServicesModel.getSelectedServiceData("price")
|
|
||||||
}
|
|
||||||
|
|
||||||
LabelWithImageType {
|
|
||||||
property bool showSubscriptionEndDate: ServersModel.getProcessedServerData("isCountrySelectionAvailable")
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 16
|
|
||||||
|
|
||||||
imageSource: "qrc:/images/controls/history.svg"
|
|
||||||
leftText: showSubscriptionEndDate ? qsTr("Valid until") : qsTr("Work period")
|
|
||||||
rightText: showSubscriptionEndDate ? ApiServicesModel.getSelectedServiceData("endDate")
|
|
||||||
: ApiServicesModel.getSelectedServiceData("workPeriod")
|
|
||||||
|
|
||||||
visible: rightText !== ""
|
visible: rightText !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelWithImageType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 16
|
|
||||||
|
|
||||||
imageSource: "qrc:/images/controls/gauge.svg"
|
|
||||||
leftText: qsTr("Speed")
|
|
||||||
rightText: ApiServicesModel.getSelectedServiceData("speed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer: ColumnLayout {
|
||||||
|
width: listView.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
ParagraphTextType {
|
ParagraphTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
@ -88,6 +127,8 @@ PageType {
|
||||||
return text.replace("%1", LanguageModel.getCurrentSiteUrl())
|
return text.replace("%1", LanguageModel.getCurrentSiteUrl())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visible: text !== ""
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ PageType {
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
textFieldPlaceholderText: qsTr("application name")
|
textField.placeholderText: qsTr("application name")
|
||||||
buttonImageSource: "qrc:/images/controls/plus.svg"
|
buttonImageSource: "qrc:/images/controls/plus.svg"
|
||||||
|
|
||||||
rightButtonClickedOnEnter: true
|
rightButtonClickedOnEnter: true
|
||||||
|
|
|
||||||
|
|
@ -221,15 +221,8 @@ PageType {
|
||||||
SettingsController.clearSettings()
|
SettingsController.clearSettings()
|
||||||
PageController.goToPageHome()
|
PageController.goToPageHome()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GC.isMobile()) {
|
|
||||||
// root.defaultActiveFocusItem.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
if (!GC.isMobile()) {
|
|
||||||
// root.defaultActiveFocusItem.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("Primary DNS")
|
headerText: qsTr("Primary DNS")
|
||||||
|
|
||||||
textFieldText: SettingsController.primaryDns
|
textField.text: SettingsController.primaryDns
|
||||||
textField.validator: RegularExpressionValidator {
|
textField.validator: RegularExpressionValidator {
|
||||||
regularExpression: InstallController.ipAddressRegExp()
|
regularExpression: InstallController.ipAddressRegExp()
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ PageType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("Secondary DNS")
|
headerText: qsTr("Secondary DNS")
|
||||||
|
|
||||||
textFieldText: SettingsController.secondaryDns
|
textField.text: SettingsController.secondaryDns
|
||||||
textField.validator: RegularExpressionValidator {
|
textField.validator: RegularExpressionValidator {
|
||||||
regularExpression: InstallController.ipAddressRegExp()
|
regularExpression: InstallController.ipAddressRegExp()
|
||||||
}
|
}
|
||||||
|
|
@ -105,19 +105,12 @@ PageType {
|
||||||
|
|
||||||
var yesButtonFunction = function() {
|
var yesButtonFunction = function() {
|
||||||
SettingsController.primaryDns = "1.1.1.1"
|
SettingsController.primaryDns = "1.1.1.1"
|
||||||
primaryDns.textFieldText = SettingsController.primaryDns
|
primaryDns.textField.text = SettingsController.primaryDns
|
||||||
SettingsController.secondaryDns = "1.0.0.1"
|
SettingsController.secondaryDns = "1.0.0.1"
|
||||||
secondaryDns.textFieldText = SettingsController.secondaryDns
|
secondaryDns.textField.text = SettingsController.secondaryDns
|
||||||
PageController.showNotificationMessage(qsTr("Settings have been reset"))
|
PageController.showNotificationMessage(qsTr("Settings have been reset"))
|
||||||
|
|
||||||
if (!GC.isMobile()) {
|
|
||||||
// defaultActiveFocusItem.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
if (!GC.isMobile()) {
|
|
||||||
// defaultActiveFocusItem.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||||
|
|
@ -132,11 +125,11 @@ PageType {
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
if (primaryDns.textFieldText !== SettingsController.primaryDns) {
|
if (primaryDns.textField.text !== SettingsController.primaryDns) {
|
||||||
SettingsController.primaryDns = primaryDns.textFieldText
|
SettingsController.primaryDns = primaryDns.textField.text
|
||||||
}
|
}
|
||||||
if (secondaryDns.textFieldText !== SettingsController.secondaryDns) {
|
if (secondaryDns.textField.text !== SettingsController.secondaryDns) {
|
||||||
SettingsController.secondaryDns = secondaryDns.textFieldText
|
SettingsController.secondaryDns = secondaryDns.textField.text
|
||||||
}
|
}
|
||||||
PageController.showNotificationMessage(qsTr("Settings saved"))
|
PageController.showNotificationMessage(qsTr("Settings saved"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ PageType {
|
||||||
Layout.topMargin: -8
|
Layout.topMargin: -8
|
||||||
Layout.bottomMargin: -8
|
Layout.bottomMargin: -8
|
||||||
|
|
||||||
|
visible: !GC.isMobile()
|
||||||
|
|
||||||
text: qsTr("Open logs folder")
|
text: qsTr("Open logs folder")
|
||||||
leftImageSource: "qrc:/images/controls/folder-open.svg"
|
leftImageSource: "qrc:/images/controls/folder-open.svg"
|
||||||
isSmallLeftImage: true
|
isSmallLeftImage: true
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ PageType {
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
headerText: qsTr("Server name")
|
headerText: qsTr("Server name")
|
||||||
textFieldText: root.processedServer.name
|
textField.text: root.processedServer.name
|
||||||
textField.maximumLength: 30
|
textField.maximumLength: 30
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
@ -155,12 +155,12 @@ PageType {
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
if (serverName.textFieldText === "") {
|
if (serverName.textField.text === "") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverName.textFieldText !== root.processedServer.name) {
|
if (serverName.textField.text !== root.processedServer.name) {
|
||||||
ServersModel.setProcessedServerData("name", serverName.textFieldText);
|
ServersModel.setProcessedServerData("name", serverName.textField.text);
|
||||||
}
|
}
|
||||||
serverNameEditDrawer.closeTriggered()
|
serverNameEditDrawer.closeTriggered()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue