Compare commits

..

105 Commits

Author SHA1 Message Date
Cyril Anisimov
25a24bf38c focusControl => FocusControl 2024-12-30 18:10:25 +01:00
Cyril Anisimov
5e492e8392 remove debug output from focusControl 2024-12-30 18:05:31 +01:00
Cyril Anisimov
b25d911328 remove debug output from ListViewFocusController 2024-12-30 18:04:05 +01:00
Cyril Anisimov
caf29fb982 remove debug output from FocusController 2024-12-30 18:03:26 +01:00
Cyril Anisimov
32ef1eae84 fix PageDevMenu 2024-12-30 00:19:11 +01:00
Cyril Anisimov
b0fdbe6c4f fix PageSettingsAppSplitTunneling 2024-12-29 22:11:35 +01:00
Cyril Anisimov
dccfcde6f6 fix PageProtocolCloakSettings 2024-12-29 22:04:00 +01:00
Cyril Anisimov
26ba520022 fix ListViewWithLabelsType 2024-12-29 22:01:41 +01:00
Cyril Anisimov
0f98fc955f remove comments and clean up 2024-12-29 21:19:56 +01:00
Cyril Anisimov
2c1f3eaf3a refactor PageSetupWizardProtocols 2024-12-29 20:26:22 +01:00
Cyril Anisimov
867ad8ee3c fix focus movement on dynamic delegates in listView 2024-12-29 00:34:20 +01:00
Cyril Anisimov
f8f4d75cb7 Merge commit 'b88ab8e432fbd2ab5002856cd83ef57181a6d7fa' into improve_navigation_cpp 2024-12-23 09:36:55 +01:00
Cyril Anisimov
13e680f6e1 fix PageSetupWizardApiServicesList 2024-12-22 16:38:07 +01:00
Cyril Anisimov
8767f487b8 fix ScrollBarType 2024-12-22 16:28:13 +01:00
Cyril Anisimov
0cdd2ff826 fix CMakeLists.txt 2024-12-20 15:35:40 +01:00
Cyril Anisimov
c973b8868b rename focusControl to qmlUtils 2024-12-20 15:30:20 +01:00
Cyril Anisimov
53aa14a2ee fix back button on SelectLanguageDrawer 2024-12-20 02:26:05 +01:00
Cyril Anisimov
b29b5962d2 I have nothing is visible only on start screen 2024-12-20 02:17:50 +01:00
Cyril Anisimov
441a2f6df6 Restore from backup is visible only on start screen 2024-12-20 02:15:48 +01:00
Cyril Anisimov
fa64c8d68d fix focus movement in ServersListView 2024-12-20 02:05:28 +01:00
Cyril Anisimov
ae31bafeb9 remove arrow from Close application item 2024-12-19 00:22:59 +01:00
Cyril Anisimov
14d67503a8 add printSectionName method to listViewController 2024-12-19 00:00:36 +01:00
Cyril Anisimov
1029142b47 refactor focusConroller according to focusControl 2024-12-18 23:59:14 +01:00
Cyril Anisimov
7670d9dace refactor listViewFocusController acoording to focusControl 2024-12-18 23:57:08 +01:00
Cyril Anisimov
2266702a0b add focusControl with utility functions for
focus control
2024-12-18 23:47:52 +01:00
Cyril Anisimov
11d20974f0 format FocusController 2024-12-16 22:12:15 +01:00
Cyril Anisimov
a57e94e4f2 format ListViewFocusController 2024-12-16 22:12:00 +01:00
Cyril Anisimov
9620a24d6a remove manual focus control on PageShare 2024-12-15 23:23:15 +01:00
Cyril Anisimov
62be292833 Fix list view focus moving on PageShare 2024-12-15 23:14:52 +01:00
Cyril Anisimov
10e91b06f4 PageShare readonly properties 2024-12-15 23:12:59 +01:00
Cyril Anisimov
3a62f1f20b fix hide/unhide password 2024-12-14 23:44:15 +01:00
Cyril Anisimov
3021066180 refactor SwitcherType 2024-12-14 19:34:24 +01:00
Cyril Anisimov
378db1a9e5 make unchangable properties readonly 2024-12-14 19:33:54 +01:00
Cyril Anisimov
906cf7b939 fix split tunneling settings 2024-12-14 19:32:46 +01:00
Cyril Anisimov
8bc31b60a1 fix checked item in lists 2024-12-14 19:29:35 +01:00
Cyril Anisimov
149c9d12d3 Merge commit '367789bda28f33a11a72d61b222453c5b502299e' into improve_navigation_cpp 2024-12-14 18:38:41 +01:00
Cyril Anisimov
b33b97802e update logging settings page 2024-12-14 18:01:34 +01:00
Cyril Anisimov
d18f50a3e0 Update select language list 2024-12-13 22:27:21 +01:00
Cyril Anisimov
ef2ffce47a Fix selection in language change list 2024-12-13 22:14:27 +01:00
Cyril Anisimov
53f41408b8 Scrollbar is on if the content is larger than a screen 2024-12-13 22:09:37 +01:00
albexk
5b9ba8c027 fix: get rid of the assign function call 2024-12-09 20:56:58 +03:00
Cyril Anisimov
882c288d92 make minor fixes and clean up 2024-12-09 01:53:48 +01:00
Cyril Anisimov
ea39447708 fix focus behavior on new page/popup 2024-12-09 01:35:10 +01:00
Cyril Anisimov
3cc70790bd add setDelegateIndex method to listViewFocusController 2024-12-09 01:32:11 +01:00
Cyril Anisimov
f331c11f51 refactor credentials setup page to handle the focus navigation 2024-12-09 01:30:11 +01:00
Cyril Anisimov
39cbe6de28 update initial config page 2024-12-09 01:28:57 +01:00
Cyril Anisimov
87d00116e1 add ScrollBarType 2024-12-06 22:59:12 +01:00
Cyril Anisimov
f992252c6e Merge commit into improve_navigation_cpp 2024-12-02 11:10:30 +01:00
albexk
c9205afbd4 chore: bump version code 2024-11-25 14:08:25 +03:00
albexk
09b1f322ba fix: hide UI elements that use file saving 2024-11-25 13:56:14 +03:00
albexk
4d2d7e4071 fix: add the touch emulation method for Android TV 2024-11-25 12:33:34 +03:00
albexk
c72d76aec7 Merge branch 'improve_navigation_cpp' into feature/android-tv 2024-11-25 12:20:31 +03:00
albexk
50bd364f29 Merge branch 'dev' into feature/android-tv
# Conflicts:
#	CMakeLists.txt
2024-11-25 12:19:52 +03:00
albexk
3eeab2f2e7 fix: null uri processing 2024-11-25 12:17:22 +03:00
albexk
2987e03a86 fix: add the touch emulation method for Android TV 2024-11-25 12:17:22 +03:00
albexk
392cacaabc refactor: make TvFilePicker activity more sustainable 2024-11-25 12:17:22 +03:00
albexk
51092e91cf fix: change the banner format for TV 2024-11-25 12:17:22 +03:00
albexk
d02a6dfb22 fix: add a workaround to open files on Android TV due to lack of SAF 2024-11-25 12:17:22 +03:00
Nethius
4e1862a802 chore: minor fixes (#1235) 2024-11-25 12:15:36 +03:00
albexk
53e766f6d3 Fix connection check for AWG/WG 2024-11-25 12:15:36 +03:00
albexk
341d6f569b fix(android): add CHANGE_NETWORK_STATE permission for all Android versions 2024-11-25 12:15:36 +03:00
albexk
a5abab8caf fix: add separate method for reading files to fix file reading on Android TV 2024-11-25 12:15:36 +03:00
albexk
7df050371e Fix importing files on TVs 2024-11-25 12:15:36 +03:00
albexk
65870ad5c9 Add mandatory requirement for android.software.leanback. 2024-11-25 12:15:36 +03:00
albexk
940806a6fa Bump version 2024-11-25 12:15:36 +03:00
Cyril Anisimov
0620b4536a update popup 2024-11-21 19:11:57 +01:00
Cyril Anisimov
d956be901d refactor page logging settings for focus navigation 2024-11-21 19:11:57 +01:00
Cyril Anisimov
942805cca2 fix focus move on qr code on share page 2024-11-21 19:11:57 +01:00
Cyril Anisimov
ed6fc27e52 fix focus navigation in dynamic delegate of list view 2024-11-21 19:11:57 +01:00
Cyril Anisimov
416421cba7 fix crash on return with esc key 2024-11-21 19:11:57 +01:00
Cyril Anisimov
45b8235a81 update config page for scrolling with tab 2024-11-21 19:11:57 +01:00
Cyril Anisimov
a92f706524 fix focus on notification 2024-11-21 19:11:57 +01:00
Cyril Anisimov
5e9202f6ac refactor focusController to make the logic more
straightforward
2024-11-21 19:11:57 +01:00
Cyril Anisimov
dc6c1cdf49 refactor listViewFocusController 2024-11-21 19:11:57 +01:00
Cyril Anisimov
f020bdb6e8 fix drawer open close usage 2024-11-21 19:11:57 +01:00
Cyril Anisimov
88958c042f fix share details drawer 2024-11-21 19:11:57 +01:00
Cyril Anisimov
e4d21dc4d7 fix spawn signals on switch 2024-11-21 19:11:57 +01:00
Cyril Anisimov
2e896ed34f fix server list back button handler 2024-11-21 19:11:57 +01:00
Cyril Anisimov
163399816f minor fixes 2024-11-21 19:11:57 +01:00
Cyril Anisimov
3d7209ee7a fix focus move on list views with header and-or footer 2024-11-21 19:11:57 +01:00
Cyril Anisimov
42645a98f8 refactor about page for correct focus move 2024-11-21 19:11:57 +01:00
Cyril Anisimov
7c3d08d80e fix page share default server selection 2024-11-21 19:11:57 +01:00
Cyril Anisimov
c77e01fb37 fix server rename 2024-11-21 19:11:57 +01:00
Cyril Anisimov
b6c59b08a1 clean up page share 2024-11-21 19:11:56 +01:00
Cyril Anisimov
766e1c92fc fix focus on users on page share 2024-11-21 19:11:56 +01:00
Cyril Anisimov
21755cbd54 fix protocol settings focus move 2024-11-21 19:11:56 +01:00
Cyril Anisimov
dac45a9f7f fix crashes after ListView navigation 2024-11-21 19:11:56 +01:00
Cyril Anisimov
db5d289edc fix back navigation on default focus item 2024-11-21 19:11:56 +01:00
Cyril Anisimov
c962211068 fix PageSetupWizardProtocolSettings focus move 2024-11-21 19:11:56 +01:00
Cyril Anisimov
9cfa4c1389 fix drawer layout and focus move 2024-11-21 19:11:56 +01:00
Cyril Anisimov
2c9fa10b8b fix drawer radio buttons selection 2024-11-21 19:11:56 +01:00
Cyril Anisimov
626b9e1e76 fix reverse focus move on listView 2024-11-21 19:11:56 +01:00
Cyril Anisimov
ada3f9a7fa remove useless slots, logs, Drawer open and close 2024-11-21 19:11:56 +01:00
Cyril Anisimov
063851445a remove useless key navigation 2024-11-21 19:11:56 +01:00
Cyril Anisimov
852e90e317 update CardType for using with focus navigation 2024-11-21 19:11:56 +01:00
Cyril Anisimov
89ac585e07 fix ListView navigation 2024-11-21 19:11:56 +01:00
Cyril Anisimov
f3df9eb5f5 add ListViewFocusController 2024-11-21 19:11:56 +01:00
Cyril Anisimov
75f189e256 update pages 2024-11-21 19:11:56 +01:00
Cyril Anisimov
3c655d0051 update transitions 2024-11-21 19:11:56 +01:00
Cyril Anisimov
b4f4ec4ac9 add default focus item 2024-11-21 19:11:56 +01:00
Cyril Anisimov
f189f7b6af add reverse focus change to FocusController 2024-11-21 19:11:56 +01:00
Cyril Anisimov
78a4caa47e fixed language selector 2024-11-21 19:11:56 +01:00
Cyril Anisimov
01e31b4b4d add focus navigation to qml 2024-11-21 19:11:56 +01:00
Cyril Anisimov
cecee3769e add more key handlers 2024-11-21 19:11:55 +01:00
Cyril Anisimov
02bbcd3a31 add focusController class 2024-11-21 19:11:55 +01:00
119 changed files with 5990 additions and 7481 deletions

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "client/3rd/OpenVPNAdapter"]
path = client/3rd/OpenVPNAdapter
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.8.3.3
project(${PROJECT} VERSION 4.8.3.0
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2076)
set(APP_ANDROID_VERSION_CODE 2072)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -13,13 +13,13 @@
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
@@ -185,7 +185,7 @@ GPL v3.0
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>

View File

@@ -10,12 +10,12 @@
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
@@ -169,7 +169,7 @@ GPL v3.0
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>

1
client/3rd/OpenVPNAdapter vendored Submodule

View File

@@ -96,6 +96,11 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- 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})
if(IS_CI)
message("Detected CI env")

View File

@@ -2,8 +2,6 @@
#include <QClipboard>
#include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
@@ -12,6 +10,8 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QLocalSocket>
#include <QLocalServer>
#include "logger.h"
#include "ui/models/installedAppsModel.h"
@@ -282,17 +282,16 @@ bool AmneziaApplication::parseCommands()
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer *server = new QLocalServer(this);
QLocalServer* server = new QLocalServer(this);
server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket *clientConnection = server->nextPendingConnection();
QLocalSocket* clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
}
emit m_pageController->raiseMainWindow();
@@ -419,9 +418,7 @@ void AmneziaApplication::initControllers()
&ConnectionController::onCurrentContainerUpdated);
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
if (m_reloadConfigErrorOccurredConnection) {
disconnect(m_reloadConfigErrorOccurredConnection);
}
disconnect(m_reloadConfigErrorOccurredConnection);
emit m_connectionController->configFromApiUpdated();
});
@@ -429,7 +426,7 @@ void AmneziaApplication::initControllers()
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[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(), "", "");
});
@@ -437,7 +434,7 @@ void AmneziaApplication::initControllers()
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[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_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
});

View File

@@ -11,7 +11,7 @@
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<!-- for TV -->
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="true" />
<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

View File

@@ -76,7 +76,11 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
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
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
@@ -122,9 +126,9 @@ add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
)
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-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)
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")

View File

@@ -120,7 +120,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
}
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
{
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) {

View File

@@ -110,19 +110,22 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
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.") },
{ DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
"may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probing detection. It is very resistant to detection, but offers low speed.") },
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") },
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
"consumption. Recommended for regions with low levels of censorship.") },
{ DockerContainer::Awg,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
"but very resistant to blockages. "
"Recommended for regions with high levels of censorship.") },
{ DockerContainer::Xray,
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
"It is highly resistant to detection and offers high speed.") },
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
{ DockerContainer::Ipsec,
QObject::tr("IKEv2/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.") },
@@ -141,20 +144,20 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
return {
{ DockerContainer::OpenVpn,
QObject::tr(
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
"It employs its unique security protocol, "
"leveraging the strength of SSL/TLS for encryption and key exchange. "
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
"catering to a wide range of devices and operating systems. "
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
"which continually reinforces its security. "
"With a strong balance of performance, security, and compatibility, "
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Normal power consumption on mobile devices\n"
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
"* Recognised by DPI systems and therefore susceptible to blocking\n"
"* Can operate over both TCP and UDP network protocols.") },
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
"It employs its unique security protocol, "
"leveraging the strength of SSL/TLS for encryption and key exchange. "
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
"catering to a wide range of devices and operating systems. "
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
"which continually reinforces its security. "
"With a strong balance of performance, security, and compatibility, "
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Normal power consumption on mobile 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"
"* Can operate over both TCP and UDP network protocols.") },
{ DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
@@ -166,26 +169,28 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") },
{ DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"protecting against detection.\n\n"
"protecting against blocking.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n"
"Cloak protects OpenVPN from detection. \n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n"
"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 "
"being detected\n\n"
"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 "
"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"
"* High power consumption on mobile devices\n"
"* Flexible settings\n"
"* Not recognised by detection systems\n"
"* Not recognised by DPI analysis systems\n"
"* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard,
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 "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, "
"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"
@@ -208,18 +213,18 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n"
"* Minimum number of settings\n"
"* Not recognised by traffic analysis systems\n"
"* Not recognised by DPI analysis systems, resistant to blocking\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Xray,
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
"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"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
"This makes REALITY a robust solution for maintaining internet freedom.")
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
"thus presenting an authentic TLS certificate and data. \n"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
},
{ DockerContainer::Ipsec,
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
@@ -327,7 +332,9 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
// case DockerContainer::Cloak: return true;
default: return false;
}
}
@@ -335,7 +342,9 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return tr("Automatic");
case DockerContainer::WireGuard: return tr("Low");
case DockerContainer::Awg: return tr("High");
// case DockerContainer::Cloak: return tr("Extreme");
default: return "";
}
}
@@ -343,8 +352,10 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
// case DockerContainer::Cloak:
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
default: return "";
}
}
@@ -352,7 +363,9 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return 1;
case DockerContainer::WireGuard: return 3;
case DockerContainer::Awg: return 2;
// case DockerContainer::Cloak: return 1;
default: return 0;
}
}
@@ -371,9 +384,9 @@ bool ContainerProps::isShareable(DockerContainer container)
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
.toObject()
.value(config_key::last_config)
.toString();
.toObject()
.value(config_key::last_config)
.toString();
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
}

View File

@@ -308,8 +308,6 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
} else if (reply->error() == QNetworkReply::NetworkError::SslHandshakeFailedError) {
emit errorOccurred(ErrorCode::ApiConfigSslError);
} else {
QString err = reply->errorString();
qDebug() << QString::fromUtf8(reply->readAll());
@@ -325,8 +323,10 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
QObject::connect(reply, &QNetworkReply::errorOccurred,
[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);
});
}
}

View File

@@ -346,9 +346,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::Awg) {
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)
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
@@ -372,10 +370,8 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::WireGuard) {
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::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
return true;
}
@@ -611,8 +607,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
// 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({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });

View File

@@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes
server.users.first().security = "auto";
}
const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
if (query.hasQueryItem(key))
return query.queryItemValue(key, QUrl::FullyDecoded);
else

View File

@@ -114,23 +114,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
// Bring up the wireguard interface if not already done.
if (!wgutils()->interfaceExists()) {
// Create the interface.
if (!wgutils()->addInterface(config)) {
logger.error() << "Interface creation failed.";
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.
for (const QString& i : config.m_excludedAddresses) {
addExclusionRoute(IPAddress(i));
@@ -146,6 +135,15 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
if (supportIPUtils()) {
if (!iputils()->addInterfaceIPs(config)) {
return false;
}
if (!iputils()->setMTUAndUp(config)) {
return false;
}
}
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {

View File

@@ -8,8 +8,6 @@
#include <QDateTime>
#include <QTimer>
#include "daemon/daemonerrors.h"
#include "daemonerrors.h"
#include "dnsutils.h"
#include "interfaceconfig.h"
#include "iputils.h"
@@ -53,7 +51,7 @@ class Daemon : public QObject {
*/
void activationFailure();
void disconnected();
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
void backendFailure();
private:
bool maybeUpdateResolvers(const InterfaceConfig& config);

View File

@@ -1,17 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#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,
};

View File

@@ -159,10 +159,9 @@ void DaemonLocalServerConnection::disconnected() {
write(obj);
}
void DaemonLocalServerConnection::backendFailure(DaemonError err) {
void DaemonLocalServerConnection::backendFailure() {
QJsonObject obj;
obj.insert("type", "backendFailure");
obj.insert("errorCode", static_cast<int>(err));
write(obj);
}

View File

@@ -7,8 +7,6 @@
#include <QObject>
#include "daemonerrors.h"
class QLocalSocket;
class DaemonLocalServerConnection final : public QObject {
@@ -25,7 +23,7 @@ class DaemonLocalServerConnection final : public QObject {
void connected(const QString& pubkey);
void disconnected();
void backendFailure(DaemonError err);
void backendFailure();
void write(const QJsonObject& obj);

View File

@@ -45,11 +45,9 @@ class WireguardUtils : public QObject {
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
virtual bool excludeLocalNetworks(const QList<IPAddress>& addresses) = 0;
};
#endif // WIREGUARDUTILS_H

View File

@@ -1,5 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 644 B

View File

@@ -27,7 +27,12 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
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

19
client/ios/scripts/openvpn.sh Executable file
View File

@@ -0,0 +1,19 @@
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 ../../

View File

@@ -1,10 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "protocols/protocols_defs.h"
#include "localsocketcontroller.h"
#include <stdint.h>
#include <QDir>
#include <QFileInfo>
#include <QHostAddress>
@@ -18,9 +17,6 @@
#include "leakdetector.h"
#include "logger.h"
#include "models/server.h"
#include "daemon/daemonerrors.h"
#include "protocols/protocols_defs.h"
// How many times do we try to reconnect.
constexpr int MAX_CONNECTION_RETRY = 10;
@@ -455,39 +451,8 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
}
if (type == "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;
}
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;
}
qCritical() << "backendFailure";
return;
}
if (type == "logs") {

View File

@@ -1,5 +1,4 @@
import HevSocks5Tunnel
import NetworkExtension
public enum Socks5Tunnel {

View File

@@ -196,8 +196,6 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
}
return result;
}
@@ -279,7 +277,6 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
QStringLiteral("-o tun2+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});

View File

@@ -297,6 +297,31 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
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) {
if (!m_rtmonitor) {
return false;
@@ -352,26 +377,6 @@ bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& 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) {
QLocalSocket socket;
QTimer uapiTimeout;
@@ -445,27 +450,3 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
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);
}

View File

@@ -37,9 +37,6 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
void applyFirewallRules(FirewallParams& params);
signals:
void backendFailure();

View File

@@ -358,8 +358,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
}
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
unsigned int ifindex, const void* gateway,
int flags) {
unsigned int ifindex,
const void* gateway) {
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
sizeof(struct sockaddr_in6) * 2 +
sizeof(struct sockaddr_storage);
@@ -370,7 +370,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = action;
rtm->rtm_index = ifindex;
rtm->rtm_flags = flags | RTF_STATIC | RTF_UP;
rtm->rtm_flags = RTF_STATIC | RTF_UP;
rtm->rtm_addrs = 0;
rtm->rtm_pid = 0;
rtm->rtm_seq = m_rtseq++;
@@ -490,7 +490,7 @@ bool MacosRouteMonitor::rtmFetchRoutes(int family) {
return false;
}
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) {
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
struct sockaddr_dl datalink;
memset(&datalink, 0, sizeof(datalink));
datalink.sdl_family = AF_LINK;
@@ -502,11 +502,11 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) {
datalink.sdl_slen = 0;
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink, flags);
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
}
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) {
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr, flags);
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
}
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {

View File

@@ -24,8 +24,8 @@ class MacosRouteMonitor final : public QObject {
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
~MacosRouteMonitor();
bool insertRoute(const IPAddress& prefix, int flags = 0);
bool deleteRoute(const IPAddress& prefix, int flags = 0);
bool insertRoute(const IPAddress& prefix);
bool deleteRoute(const IPAddress& prefix);
int interfaceFlags() { return m_ifflags; }
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 handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
const void* gateway, int flags = 0);
const void* gateway);
bool rtmFetchRoutes(int family);
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
const void* sa);

View File

@@ -5,7 +5,6 @@
#include "wireguardutilsmacos.h"
#include <errno.h>
#include <net/route.h>
#include <QByteArray>
#include <QDir>
@@ -131,6 +130,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
} else {
@@ -211,6 +211,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
logger.warning() << "Failed to create peer with no endpoints";
return false;
}
out << config.m_serverPort << "\n";
out << "replace_allowed_ips=true\n";
@@ -322,10 +323,10 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
if (prefix.prefixLength() > 0) {
return m_rtmonitor->deleteRoute(prefix);
return m_rtmonitor->insertRoute(prefix);
}
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
@@ -345,6 +346,31 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& 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) {
if (!m_rtmonitor) {
return false;
@@ -352,26 +378,6 @@ bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& 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) {
QLocalSocket socket;
QTimer uapiTimeout;
@@ -448,28 +454,3 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
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);
}

View File

@@ -35,9 +35,6 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
void applyFirewallRules(FirewallParams& params);
signals:

View File

@@ -5,7 +5,6 @@
#include "windowsdaemon.h"
#include <Windows.h>
#include <qassert.h>
#include <QCoreApplication>
#include <QJsonDocument>
@@ -16,34 +15,28 @@
#include <QTextStream>
#include <QtGlobal>
#include "daemon/daemonerrors.h"
#include "dnsutilswindows.h"
#include "leakdetector.h"
#include "logger.h"
#include "platforms/windows/daemon/windowsfirewall.h"
#include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowscommons.h"
#include "windowsfirewall.h"
#include "core/networkUtilities.h"
#include "platforms/windows/windowscommons.h"
#include "platforms/windows/windowsservicemanager.h"
#include "windowsfirewall.h"
namespace {
Logger logger("WindowsDaemon");
}
WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) {
MZ_COUNT_CTOR(WindowsDaemon);
m_firewallManager = WindowsFirewall::create(this);
Q_ASSERT(m_firewallManager != nullptr);
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
m_wgutils = new WireguardUtilsWindows(this);
m_dnsutils = new DnsUtilsWindows(this);
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this,
&WindowsDaemon::monitorBackendFailure);
connect(this, &WindowsDaemon::activationFailure,
[this]() { m_firewallManager->disableKillSwitch(); });
[]() { WindowsFirewall::instance()->disableKillSwitch(); });
}
WindowsDaemon::~WindowsDaemon() {
@@ -64,42 +57,28 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
} else {
m_splitTunnelManager->stop();
m_splitTunnelManager.stop();
}
}
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
if (!m_splitTunnelManager) {
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);
}
if (op == Down) {
m_splitTunnelManager.stop();
return true;
}
if (op == Down) {
m_splitTunnelManager->stop();
return true;
}
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);
if (op == Up) {
logger.debug() << "Tunnel UP, Starting SplitTunneling";
if (!WindowsSplitTunnel::isInstalled()) {
logger.warning() << "Split Tunnel Driver not Installed yet, fixing this.";
WindowsSplitTunnel::installDriver();
}
return true;
}
m_splitTunnelManager->stop();
activateSplitTunnel(config);
return true;
}

View File

@@ -5,11 +5,8 @@
#ifndef WINDOWSDAEMON_H
#define WINDOWSDAEMON_H
#include <qpointer.h>
#include "daemon/daemon.h"
#include "dnsutilswindows.h"
#include "windowsfirewall.h"
#include "windowssplittunnel.h"
#include "windowstunnelservice.h"
#include "wireguardutilswindows.h"
@@ -28,7 +25,7 @@ class WindowsDaemon final : public Daemon {
protected:
bool run(Op op, const InterfaceConfig& config) override;
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
WireguardUtils* wgutils() const override { return m_wgutils; }
DnsUtils* dnsutils() override { return m_dnsutils; }
private:
@@ -42,10 +39,9 @@ class WindowsDaemon final : public Daemon {
int m_inetAdapterIndex = -1;
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
WireguardUtilsWindows* m_wgutils = nullptr;
DnsUtilsWindows* m_dnsutils = nullptr;
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
QPointer<WindowsFirewall> m_firewallManager;
WindowsSplitTunnel m_splitTunnelManager;
};
#endif // WINDOWSDAEMON_H

View File

@@ -9,12 +9,11 @@
#include <guiddef.h>
#include <initguid.h>
#include <netfw.h>
#include <qaccessible.h>
#include <qassert.h>
//#include <qaccessible.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <windows.h>
#include <Ws2tcpip.h>
#include "winsock.h"
#include <QApplication>
#include <QFileInfo>
@@ -28,6 +27,7 @@
#include "leakdetector.h"
#include "logger.h"
#include "platforms/windows/windowsutils.h"
#include "winsock.h"
#define IPV6_ADDRESS_SIZE 16
@@ -49,13 +49,18 @@ constexpr uint8_t HIGH_WEIGHT = 13;
constexpr uint8_t MAX_WEIGHT = 15;
} // namespace
WindowsFirewall* WindowsFirewall::create(QObject* parent) {
if (s_instance != nullptr) {
// Only one instance of the firewall is allowed
// Q_ASSERT(false);
return s_instance;
WindowsFirewall* WindowsFirewall::instance() {
if (s_instance == nullptr) {
s_instance = new WindowsFirewall(qApp);
}
HANDLE engineHandle = nullptr;
return s_instance;
}
WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(WindowsFirewall);
Q_ASSERT(s_instance == nullptr);
HANDLE engineHandle = NULL;
DWORD result = ERROR_SUCCESS;
// Use dynamic sessions for efficiency and safety:
// -> Filtering policy objects are deleted even when the application crashes/
@@ -66,24 +71,15 @@ WindowsFirewall* WindowsFirewall::create(QObject* parent) {
logger.debug() << "Opening the filter engine.";
result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session,
&engineHandle);
result =
FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
if (result != ERROR_SUCCESS) {
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
return nullptr;
return;
}
logger.debug() << "Filter engine opened successfully.";
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);
m_sessionHandle = engineHandle;
}
WindowsFirewall::~WindowsFirewall() {
@@ -93,8 +89,15 @@ WindowsFirewall::~WindowsFirewall() {
}
}
// static
bool WindowsFirewall::initSublayer() {
bool WindowsFirewall::init() {
if (m_init) {
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.
// We need to open up another handle because of wfp rules:
// If a wfp resource was created with SESSION_DYNAMIC,
@@ -154,10 +157,11 @@ bool WindowsFirewall::initSublayer() {
return false;
}
logger.debug() << "Initialised Sublayer";
m_init = true;
return true;
}
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
// Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not.
#define FW_OK(rule) \
@@ -180,7 +184,7 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
} \
}
logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex;
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter"));
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
@@ -196,36 +200,6 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
#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) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
@@ -264,10 +238,10 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (!config.m_excludedAddresses.empty()) {
for (const QString& i : config.m_excludedAddresses) {
logger.debug() << "excludedAddresses range: " << i;
logger.debug() << "range: " << i;
if (!allowTrafficTo(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) {
if (!allowTrafficToRange(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) {
return false;
}
}
@@ -447,59 +421,9 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
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,
int weight, const QString& title,
const QString& peer) {
int weight, const QString& title,
const QString& peer) {
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
GUID layerOut =
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
@@ -560,6 +484,57 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
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) {
// Allow outbound DHCPv4
{
@@ -759,7 +734,7 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
FWPM_FILTER_CONDITION0 cond[1] = {};
FWPM_FILTER_CONDITION0 cond[1] = {0};
FWP_RANGE0 ipRange;
QByteArray lowIpV6Buffer;
QByteArray highIpV6Buffer;

View File

@@ -26,27 +26,18 @@ struct FWP_CONDITION_VALUE0_;
class WindowsFirewall final : public QObject {
public:
/**
* @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;
~WindowsFirewall();
bool enableInterface(int vpnAdapterIndex);
bool enableLanBypass(const QList<IPAddress>& ranges);
static WindowsFirewall* instance();
bool init();
bool enableKillSwitch(int vpnAdapterIndex);
bool enablePeerTraffic(const InterfaceConfig& config);
bool disablePeerTraffic(const QString& pubkey);
bool disableKillSwitch();
private:
static bool initSublayer();
WindowsFirewall(HANDLE session, QObject* parent);
WindowsFirewall(QObject* parent);
HANDLE m_sessionHandle;
bool m_init = false;
QList<uint64_t> m_activeRules;
@@ -59,10 +50,11 @@ class WindowsFirewall final : public QObject {
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
const QString& title, const QString& peer = QString());
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,
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,
const QString& title);
bool allowDHCPTraffic(uint8_t weight, const QString& title);

View File

@@ -13,12 +13,6 @@ namespace {
Logger logger("WindowsRouteMonitor");
}; // 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
// invoke the routeChanged slot to do the real work.
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
@@ -26,17 +20,22 @@ static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
Q_UNUSED(type);
// Ignore route changes that we created.
if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
(row->Metric == EXCLUSION_ROUTE_METRIC)) {
return;
}
if (monitor->getLuid() == row->InterfaceLuid.Value) {
// Ignore host route changes, and unsupported protocols.
if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
if (row->DestinationPrefix.PrefixLength >= 128) {
return;
}
} else if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
if (row->DestinationPrefix.PrefixLength >= 32) {
return;
}
} else {
return;
}
// Invoke the route changed signal to do the real work in Qt.
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
if (monitor->getLuid() != row->InterfaceLuid.Value) {
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
}
}
// Perform prefix matching comparison on IP addresses in host order.
@@ -58,8 +57,7 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
return 0;
}
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
: QObject(parent), m_luid(luid) {
WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(WindowsRouteMonitor);
logger.debug() << "WindowsRouteMonitor created.";
@@ -69,13 +67,11 @@ WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
WindowsRouteMonitor::~WindowsRouteMonitor() {
MZ_COUNT_DTOR(WindowsRouteMonitor);
CancelMibChangeNotify2(m_routeHandle);
flushRouteTable(m_exclusionRoutes);
flushRouteTable(m_clonedRoutes);
flushExclusionRoutes();
logger.debug() << "WindowsRouteMonitor destroyed.";
}
void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
void WindowsRouteMonitor::updateValidInterfaces(int family) {
PMIB_IPINTERFACE_TABLE table;
DWORD result = GetIpInterfaceTable(family, &table);
if (result != NO_ERROR) {
@@ -86,10 +82,10 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
// Flush the list of interfaces that are valid for routing.
if ((family == AF_INET) || (family == AF_UNSPEC)) {
m_interfaceMetricsIpv4.clear();
m_validInterfacesIpv4.clear();
}
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
m_interfaceMetricsIpv6.clear();
m_validInterfacesIpv6.clear();
}
// Rebuild the list of interfaces that are valid for routing.
@@ -105,12 +101,12 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
if (row->Family == AF_INET) {
logger.debug() << "Interface" << row->InterfaceIndex
<< "is valid for IPv4 routing";
m_interfaceMetricsIpv4[row->InterfaceLuid.Value] = row->Metric;
m_validInterfacesIpv4.append(row->InterfaceLuid.Value);
}
if (row->Family == AF_INET6) {
logger.debug() << "Interface" << row->InterfaceIndex
<< "is valid for IPv6 routing";
m_interfaceMetricsIpv6[row->InterfaceLuid.Value] = row->Metric;
m_validInterfacesIpv6.append(row->InterfaceLuid.Value);
}
}
}
@@ -130,72 +126,72 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
if (row->InterfaceLuid.Value == m_luid) {
continue;
}
if (row->DestinationPrefix.PrefixLength < bestMatch) {
// Ignore host routes, and shorter potential matches.
if (row->DestinationPrefix.PrefixLength >=
data->DestinationPrefix.PrefixLength) {
continue;
}
// Ignore routes of our own creation.
if ((row->Protocol == data->Protocol) && (row->Metric == data->Metric)) {
if (row->DestinationPrefix.PrefixLength < bestMatch) {
continue;
}
// 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 (!m_interfaceMetricsIpv6.contains(row->InterfaceLuid.Value)) {
if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) {
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;
}
routeMetric += m_interfaceMetricsIpv6[row->InterfaceLuid.Value];
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
if (!m_interfaceMetricsIpv4.contains(row->InterfaceLuid.Value)) {
if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) {
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;
}
routeMetric += m_interfaceMetricsIpv4[row->InterfaceLuid.Value];
} else {
// Unsupported destination address family.
continue;
}
if (routeMetric < row->Metric) {
routeMetric = ULONG_MAX;
}
// Prefer routes with lower metric if we find multiple matches
// with the same prefix length.
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
(routeMetric >= bestMetric)) {
(row->Metric >= bestMetric)) {
continue;
}
// If we got here, then this is the longest prefix match so far.
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
bestLuid = row->InterfaceLuid.Value;
bestMatch = row->DestinationPrefix.PrefixLength;
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;
}
bestMetric = row->Metric;
}
// 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) {
return;
}
// Delete the previous routing table entry, if any.
// Update the routing table entry.
if (data->InterfaceLuid.Value != 0) {
DWORD result = DeleteIpForwardEntry2(data);
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
logger.error() << "Failed to delete route:" << result;
}
}
// Update the routing table entry.
data->InterfaceLuid.Value = bestLuid;
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
if (data->InterfaceLuid.Value != 0) {
@@ -206,178 +202,10 @@ 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) {
logger.debug() << "Adding exclusion route for"
<< 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)) {
logger.warning() << "Exclusion route already exists";
return false;
@@ -404,7 +232,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
// Set the rest of the flags for a static route.
data->ValidLifetime = 0xffffffff;
data->PreferredLifetime = 0xffffffff;
data->Metric = EXCLUSION_ROUTE_METRIC;
data->Metric = 0;
data->Protocol = MIB_IPPROTO_NETMGMT;
data->Loopback = false;
data->AutoconfigureAddress = false;
@@ -426,8 +254,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
delete data;
return false;
}
updateInterfaceMetrics(family);
updateCapturedRoutes(family, table);
updateValidInterfaces(family);
updateExclusionRoute(data, table);
FreeMibTable(table);
@@ -439,28 +266,26 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(prefix.address().toString());
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
if (data == nullptr) {
return true;
for (;;) {
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
if (data == nullptr) {
break;
}
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
logger.error() << "Failed to delete route to"
<< logger.sensitive(prefix.toString())
<< "result:" << result;
}
delete data;
}
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
logger.error() << "Failed to delete route to"
<< logger.sensitive(prefix.toString())
<< "result:" << result;
}
// Captured routes might have changed.
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
delete data;
return true;
}
void WindowsRouteMonitor::flushRouteTable(
QHash<IPAddress, MIB_IPFORWARD_ROW2*>& table) {
for (auto i = table.begin(); i != table.end(); i++) {
void WindowsRouteMonitor::flushExclusionRoutes() {
for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) {
MIB_IPFORWARD_ROW2* data = i.value();
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
@@ -470,17 +295,7 @@ void WindowsRouteMonitor::flushRouteTable(
}
delete data;
}
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;
}
m_exclusionRoutes.clear();
}
void WindowsRouteMonitor::routeChanged() {
@@ -493,8 +308,7 @@ void WindowsRouteMonitor::routeChanged() {
return;
}
updateInterfaceMetrics(AF_UNSPEC);
updateCapturedRoutes(AF_UNSPEC, table);
updateValidInterfaces(AF_UNSPEC);
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
updateExclusionRoute(data, table);
}

View File

@@ -11,8 +11,6 @@
#include <winsock2.h>
#include <ws2ipdef.h>
#include <QHash>
#include <QMap>
#include <QObject>
#include "ipaddress.h"
@@ -21,41 +19,28 @@ class WindowsRouteMonitor final : public QObject {
Q_OBJECT
public:
WindowsRouteMonitor(quint64 luid, QObject* parent);
WindowsRouteMonitor(QObject* parent);
~WindowsRouteMonitor();
void setDetaultRouteCapture(bool enable);
bool addExclusionRoute(const IPAddress& prefix);
bool deleteExclusionRoute(const IPAddress& prefix);
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
void flushExclusionRoutes();
quint64 getLuid() const { return m_luid; }
void setLuid(quint64 luid) { m_luid = luid; }
quint64 getLuid() { return m_luid; }
public slots:
void routeChanged();
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 updateInterfaceMetrics(int family);
void updateCapturedRoutes(int family);
void updateCapturedRoutes(int family, void* table);
void updateValidInterfaces(int family);
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
QList<quint64> m_validInterfacesIpv4;
QList<quint64> m_validInterfacesIpv6;
// Default route cloning
bool m_defaultRouteCapture = false;
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_clonedRoutes;
const quint64 m_luid = 0;
quint64 m_luid = 0;
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
};

View File

@@ -4,15 +4,9 @@
#include "windowssplittunnel.h"
#include <qassert.h>
#include <memory>
#include "../windowscommons.h"
#include "../windowsservicemanager.h"
#include "logger.h"
#include "platforms/windows/daemon/windowsfirewall.h"
#include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowsutils.h"
#include "windowsfirewall.h"
@@ -24,252 +18,34 @@
#include <QFileInfo>
#include <QNetworkInterface>
#include <QScopeGuard>
#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
#include <QThread>
namespace {
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;
}
} // 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.
WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
if (detectConflict()) {
logger.error() << "Conflict detected, abort Split-Tunnel init.";
return nullptr;
uninstallDriver();
return;
}
// 01: Check if the driver is installed, if not do so.
m_tries = 0;
if (!isInstalled()) {
logger.debug() << "Driver is not Installed, doing so";
auto handle = installDriver();
if (handle == INVALID_HANDLE_VALUE) {
WindowsUtils::windowsLog("Failed to install Driver");
return nullptr;
return;
}
logger.debug() << "Driver installed";
CloseServiceHandle(handle);
} else {
logger.debug() << "Driver was installed";
logger.debug() << "Driver is installed";
}
// 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);
initDriver();
}
WindowsSplitTunnel::~WindowsSplitTunnel() {
@@ -277,12 +53,73 @@ WindowsSplitTunnel::~WindowsSplitTunnel() {
uninstallDriver();
}
bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
void WindowsSplitTunnel::initDriver() {
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();
if (state != STATE_READY && state != STATE_RUNNING) {
logger.warning() << "Driver is not in the right State to set Rules"
<< state;
return false;
return;
}
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
@@ -296,13 +133,12 @@ bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
auto err = GetLastError();
WindowsUtils::windowsLog("Set Config Failed:");
logger.error() << "Failed to set Config err code " << err;
return false;
return;
}
logger.debug() << "New Configuration applied: " << stateString();
return true;
logger.debug() << "New Configuration applied: " << getState();
}
bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
// To Start we need to send 2 things:
// Network info (what is vpn what is network)
logger.debug() << "Starting SplitTunnel";
@@ -315,7 +151,7 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
0, &bytesReturned, nullptr);
if (!ok) {
logger.error() << "Driver init failed";
return false;
return;
}
}
@@ -328,16 +164,16 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
nullptr);
if (!ok) {
logger.error() << "Failed to set Process Config";
return false;
return;
}
logger.debug() << "Set Process Config ok || new State:" << stateString();
logger.debug() << "Set Process Config ok || new State:" << getState();
}
if (getState() == STATE_INITIALIZED) {
logger.warning() << "Driver is still not ready after process list send";
return false;
return;
}
logger.debug() << "Driver is ready || new State:" << stateString();
logger.debug() << "Driver is ready || new State:" << getState();
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
@@ -345,10 +181,9 @@ bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
nullptr);
if (!ok) {
logger.error() << "Failed to set Network Config";
return false;
return;
}
logger.debug() << "New Network Config Applied || new State:" << stateString();
return true;
logger.debug() << "New Network Config Applied || new State:" << getState();
}
void WindowsSplitTunnel::stop() {
@@ -362,27 +197,25 @@ void WindowsSplitTunnel::stop() {
logger.debug() << "Stopping Split tunnel successfull";
}
bool WindowsSplitTunnel::resetDriver(HANDLE driverIO) {
void WindowsSplitTunnel::reset() {
DWORD bytesReturned;
auto ok = DeviceIoControl(driverIO, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
&bytesReturned, nullptr);
if (!ok) {
logger.error() << "Reset Split tunnel not successfull";
return false;
return;
}
logger.debug() << "Reset Split tunnel successfull";
return true;
}
// static
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) {
if (driverIO == INVALID_HANDLE_VALUE) {
DRIVER_STATE WindowsSplitTunnel::getState() {
if (m_driver == INVALID_HANDLE_VALUE) {
logger.debug() << "Can't query State from non Opened Driver";
return STATE_UNKNOWN;
}
DWORD bytesReturned;
SIZE_T outBuffer;
bool ok = DeviceIoControl(driverIO, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
sizeof(outBuffer), &bytesReturned, nullptr);
if (!ok) {
WindowsUtils::windowsLog("getState response failure");
@@ -392,10 +225,7 @@ WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) {
WindowsUtils::windowsLog("getState response is empty");
return STATE_UNKNOWN;
}
return static_cast<WindowsSplitTunnel::DRIVER_STATE>(outBuffer);
}
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() {
return getState(m_driver);
return static_cast<DRIVER_STATE>(outBuffer);
}
std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
@@ -443,59 +273,58 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
return outBuffer;
}
std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
int inetAdapterIndex, int vpnAdapterIndex) {
std::vector<std::byte> out(sizeof(IP_ADDRESSES_CONFIG));
std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG));
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
auto ifaces = QNetworkInterface::allInterfaces();
if (vpnAdapterIndex == 0) {
if (vpnAdapterIndex == 0) {
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
}
// Always the VPN
if (!getAddress(vpnAdapterIndex, &config->TunnelIpv4,
&config->TunnelIpv6)) {
return {};
}
// 2nd best route is usually the internet adapter
if (!getAddress(inetAdapterIndex, &config->InternetIpv4,
&config->InternetIpv6)) {
return {};
};
getAddress(vpnAdapterIndex, &config->TunnelIpv4,
&config->TunnelIpv6);
// 2nd best route
getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
return out;
}
bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
IN6_ADDR* out_ipv6) {
QNetworkInterface target =
QNetworkInterface::interfaceFromIndex(adapterIndex);
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
auto get = [&target](QAbstractSocket::NetworkLayerProtocol protocol) {
for (auto address : target.addressEntries()) {
if (address.ip().protocol() != protocol) {
continue;
// take the first v4/v6 Adress and convert to in_addr
for (auto address : target.addressEntries()) {
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) {
auto adrr = address.ip().toString();
std::wstring wstr = adrr.toStdWString();
logger.debug() << "IpV4" << logger.sensitive(adrr);
PCWSTR w_str_ip = wstr.c_str();
auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4);
if (ok != 1) {
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
}
return address.ip().toString().toStdWString();
break;
}
return std::wstring{};
};
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();
return false;
}
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() {
@@ -582,6 +411,33 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
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
SC_HANDLE WindowsSplitTunnel::installDriver() {
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
@@ -592,15 +448,15 @@ SC_HANDLE WindowsSplitTunnel::installDriver() {
return (SC_HANDLE)INVALID_HANDLE_VALUE;
}
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
auto binPath = (const wchar_t*)path.utf16();
LPCWSTR binPath = (const wchar_t*)path.utf16();
auto scm_rights = SC_MANAGER_ALL_ACCESS;
auto serviceManager = OpenSCManager(nullptr, // local computer
nullptr, // servicesActive database
auto serviceManager = OpenSCManager(NULL, // local computer
NULL, // servicesActive database
scm_rights);
auto service = CreateService(
serviceManager, DRIVER_SERVICE_NAME, displayName, SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binPath,
nullptr, nullptr, nullptr, nullptr, nullptr);
auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName,
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
binPath, nullptr, 0, nullptr, nullptr, nullptr);
CloseServiceHandle(serviceManager);
return service;
}
@@ -698,25 +554,3 @@ bool WindowsSplitTunnel::detectConflict() {
CloseServiceHandle(servicehandle);
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 {};
}

View File

@@ -8,7 +8,6 @@
#include <QObject>
#include <QString>
#include <QStringList>
#include <memory>
// Note: the ws2tcpip.h import must come before the others.
// clang-format off
@@ -19,78 +18,160 @@
#include <tlhelp32.h>
#include <windows.h>
class WindowsFirewall;
// States for GetState
enum DRIVER_STATE {
STATE_UNKNOWN = -1,
STATE_NONE = 0,
STATE_STARTED = 1,
STATE_INITIALIZED = 2,
STATE_READY = 3,
STATE_RUNNING = 4,
STATE_ZOMBIE = 5,
};
class WindowsSplitTunnel final {
#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:
/**
* @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.
*/
explicit WindowsSplitTunnel(QObject* parent);
~WindowsSplitTunnel();
// void excludeApps(const QStringList& paths);
// Excludes an Application from the VPN
bool excludeApps(const QStringList& appPaths);
void setRules(const QStringList& appPaths);
// Fetches and Pushed needed info to move to engaged mode
bool start(int inetAdapterIndex, int vpnAdapterIndex = 0);
void start(int inetAdapterIndex, int vpnAdapterIndex = 0);
// Deletes Rules and puts the driver into passive mode
void stop();
// Resets the Whole Driver
void reset();
// Returns true if the split-tunnel driver is now up and running.
bool isRunning();
// Just close connection, leave state as is
void close();
static bool detectConflict();
// States for GetState
enum DRIVER_STATE {
STATE_UNKNOWN = -1,
STATE_NONE = 0,
STATE_STARTED = 1,
STATE_INITIALIZED = 2,
STATE_READY = 3,
STATE_RUNNING = 4,
STATE_ZOMBIE = 5,
};
private:
// Installes the Kernel Driver as Driver Service
static SC_HANDLE installDriver();
static bool uninstallDriver();
static bool isInstalled();
static bool initDriver(HANDLE driverIO);
static DRIVER_STATE getState(HANDLE driverIO);
static bool resetDriver(HANDLE driverIO);
static bool detectConflict();
private slots:
void initDriver();
private:
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();
QString stateString();
int m_tries;
// Initializes the WFP Sublayer
bool initSublayer();
// Generates a Configuration for Each APP
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
// Generates a Configuration which IP's are VPN and which network
std::vector<std::byte> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
std::vector<uint8_t> generateProcessBlob();
[[nodiscard]] bool getAddress(int adapterIndex, IN_ADDR* out_ipv4,
IN6_ADDR* out_ipv6);
void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
// Collects info about an Opened Process
ProcessInfo getProcessInfo(HANDLE process,
const PROCESSENTRY32W& processMeta);
// Converts a path to a Dos Path:
// e.g C:/a.exe -> /harddisk0/a.exe

View File

@@ -24,20 +24,8 @@ namespace {
Logger logger("WireguardUtilsWindows");
}; // namespace
std::unique_ptr<WireguardUtilsWindows> WireguardUtilsWindows::create(
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) {
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent)
: WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) {
MZ_COUNT_CTOR(WireguardUtilsWindows);
logger.debug() << "WireguardUtilsWindows created.";
@@ -126,13 +114,13 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
return false;
}
m_luid = luid.Value;
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
m_routeMonitor.setLuid(luid.Value);
if (config.m_killSwitchEnabled) {
// Enable the windows firewall
NET_IFINDEX ifindex;
ConvertInterfaceLuidToIndex(&luid, &ifindex);
m_firewall->enableInterface(ifindex);
WindowsFirewall::instance()->enableKillSwitch(ifindex);
}
logger.debug() << "Registration completed";
@@ -140,11 +128,7 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
}
bool WireguardUtilsWindows::deleteInterface() {
if (m_routeMonitor) {
m_routeMonitor->deleteLater();
}
m_firewall->disableKillSwitch();
WindowsFirewall::instance()->disableKillSwitch();
m_tunnel.stop();
return true;
}
@@ -157,7 +141,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
if (config.m_killSwitchEnabled) {
// Enable the windows firewall for this peer.
m_firewall->enablePeerTraffic(config);
WindowsFirewall::instance()->enablePeerTraffic(config);
}
logger.debug() << "Configuring peer" << publicKey.toHex()
<< "via" << config.m_serverIpv4AddrIn;
@@ -187,9 +171,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
}
// Exclude the server address, except for multihop exit servers.
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString reply = m_tunnel.uapiCommand(message);
@@ -202,13 +186,13 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
// Disable the windows firewall for this peer.
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey);
QString message;
QTextStream out(&message);
@@ -254,13 +238,6 @@ void WireguardUtilsWindows::buildMibForwardRow(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;
buildMibForwardRow(prefix, &entry);
@@ -278,12 +255,6 @@ bool WireguardUtilsWindows::updateRoutePrefix(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;
buildMibForwardRow(prefix, &entry);
@@ -301,28 +272,9 @@ bool WireguardUtilsWindows::deleteRoutePrefix(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) {
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;
return m_routeMonitor.deleteExclusionRoute(prefix);
}

View File

@@ -9,21 +9,16 @@
#include <QHostAddress>
#include <QObject>
#include <QPointer>
#include "daemon/wireguardutils.h"
#include "windowsroutemonitor.h"
#include "windowstunnelservice.h"
class WindowsFirewall;
class WindowsRouteMonitor;
class WireguardUtilsWindows final : public WireguardUtils {
Q_OBJECT
public:
static std::unique_ptr<WireguardUtilsWindows> create(WindowsFirewall* fw,
QObject* parent);
WireguardUtilsWindows(QObject* parent);
~WireguardUtilsWindows();
bool interfaceExists() override { return m_tunnel.isRunning(); }
@@ -44,19 +39,15 @@ class WireguardUtilsWindows final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
bool WireguardUtilsWindows::excludeLocalNetworks(const QList<IPAddress>& addresses) override;
signals:
void backendFailure();
private:
WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw);
void buildMibForwardRow(const IPAddress& prefix, void* row);
quint64 m_luid = 0;
WindowsTunnelService m_tunnel;
QPointer<WindowsRouteMonitor> m_routeMonitor;
QPointer<WindowsFirewall> m_firewall;
WindowsRouteMonitor m_routeMonitor;
};
#endif // WIREGUARDUTILSWINDOWS_H

View File

@@ -4,7 +4,6 @@
#include "windowsservicemanager.h"
#include <QApplication>
#include <QTimer>
#include "Windows.h"
@@ -17,44 +16,35 @@ namespace {
Logger logger("WindowsServiceManager");
}
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();
WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) {
DWORD err = NULL;
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
auto manager = OpenSCManager(NULL, // local computer
NULL, // servicesActive database
scm_rights);
m_serviceManager = OpenSCManager(NULL, // local computer
NULL, // servicesActive database
scm_rights);
err = GetLastError();
if (err != NULL) {
logger.error() << " OpenSCManager failed code: " << err;
return {};
return;
}
logger.debug() << "OpenSCManager access given - " << err;
logger.debug() << "Opening Service - " << serviceName;
logger.debug() << "Opening Service - "
<< QString::fromWCharArray(serviceName);
// Try to get an elevated handle
auto serviceHandle =
OpenService(manager, // SCM database
service, // name of service
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
m_service = OpenService(m_serviceManager, // SCM database
serviceName, // name of service
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
err = GetLastError();
if (err != NULL) {
CloseServiceHandle(manager);
WindowsUtils::windowsLog("OpenService failed");
return {};
return;
}
m_has_access = true;
m_timer.setSingleShot(false);
logger.debug() << "Service manager execute access granted";
return std::make_unique<WindowsServiceManager>(manager, serviceHandle);
}
WindowsServiceManager::~WindowsServiceManager() {
@@ -95,6 +85,10 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
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?
QueryServiceStatusEx(m_service, // handle to service
SC_STATUS_PROCESS_INFO, // information level
@@ -125,6 +119,10 @@ bool WindowsServiceManager::startService() {
}
bool WindowsServiceManager::stopService() {
if (!m_has_access) {
logger.error() << "Need execute access to stop services";
return false;
}
auto state = getStatus().dwCurrentState;
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
logger.warning() << ("Service stop not possible, as its not running");

View File

@@ -12,7 +12,7 @@
#include "Winsvc.h"
/**
* @brief The WindowsServiceManager provides control over the a
* @brief The WindowsServiceManager provides control over the MozillaVPNBroker
* service via SCM
*/
class WindowsServiceManager : public QObject {
@@ -20,10 +20,7 @@ class WindowsServiceManager : public QObject {
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
public:
// 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(LPCWSTR serviceName);
~WindowsServiceManager();
// true if the Service is running
@@ -48,6 +45,8 @@ class WindowsServiceManager : public QObject {
// See
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
SERVICE_STATUS_PROCESS getStatus();
bool m_has_access = false;
LPWSTR m_serviceName;
SC_HANDLE m_serviceManager;
SC_HANDLE m_service; // Service handle with r/w priv.
DWORD m_state_target;

View File

@@ -238,7 +238,7 @@ ErrorCode Ikev2Protocol::start()
"-CipherTransformConstants GCMAES128 "
"-EncryptionMethod AES256 "
"-IntegrityCheckMethod SHA256 "
"-PfsGroup PFS2048 "
"-PfsGroup None "
"-DHGroup Group14 "
"-PassThru -Force\"")
.arg(tunnelName());

View File

@@ -1,14 +1,17 @@
#include "xrayprotocol.h"
#include "utilities.h"
#include "containers/containers_defs.h"
#include "core/networkUtilities.h"
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkInterface>
#include "core/networkUtilities.h"
#include "utilities.h"
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
VpnProtocol(configuration, parent)
{
readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface();
@@ -19,8 +22,9 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
XrayProtocol::~XrayProtocol()
{
qDebug() << "XrayProtocol::~XrayProtocol()";
XrayProtocol::stop();
QThread::msleep(200);
m_xrayProcess.close();
}
ErrorCode XrayProtocol::start()
@@ -32,6 +36,10 @@ ErrorCode XrayProtocol::start()
return lastError();
}
if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
}
#ifdef QT_DEBUG
m_xrayCfgFile.setAutoRemove(false);
#endif
@@ -43,16 +51,12 @@ ErrorCode XrayProtocol::start()
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" ");
qDebug().noquote() << "XrayProtocol::start()"
<< xrayExecPath() << args.join(" ");
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
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);
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
@@ -61,15 +65,18 @@ ErrorCode XrayProtocol::start()
#endif
});
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;
setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
emit setConnectionState(Vpn::ConnectionState::Error);
}
});
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;
setConnectionState(Vpn::ConnectionState::Disconnected);
if (exitStatus != QProcess::NormalExit) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
stop();
}
if (exitCode != 0) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}
});
m_xrayProcess.start();
m_xrayProcess.waitForStarted();
@@ -78,10 +85,11 @@ ErrorCode XrayProtocol::start()
setConnectionState(Vpn::ConnectionState::Connecting);
QThread::msleep(1000);
return startTun2Sock();
} else
return ErrorCode::XrayExecutableMissing;
}
else return ErrorCode::XrayExecutableMissing;
}
ErrorCode XrayProtocol::startTun2Sock()
{
m_t2sProcess->start();
@@ -93,68 +101,71 @@ ErrorCode XrayProtocol::startTun2Sock()
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
if (vpnState == Vpn::ConnectionState::Connected) {
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this,
[&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
if (vpnState == Vpn::ConnectionState::Connected)
{
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
#ifdef Q_OS_WIN
QThread::msleep(8000);
QThread::msleep(8000);
#endif
#ifdef Q_OS_MACOS
QThread::msleep(5000);
IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("utun22", dnsAddr);
QThread::msleep(5000);
IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("utun22", dnsAddr);
#endif
#ifdef Q_OS_LINUX
QThread::msleep(1000);
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
QThread::msleep(1000);
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
}
#endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
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_routeGateway, QStringList() << m_remoteAddress);
}
IpcClient::Interface()->StopRoutingIpv6();
#ifdef Q_OS_WIN
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) {
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enablePeerTraffic(m_configData);
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
}
#endif
if (m_routeMode == 0) {
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_routeGateway, QStringList() << m_remoteAddress);
}
IpcClient::Interface()->StopRoutingIpv6();
#ifdef Q_OS_WIN
IpcClient::Interface()->updateResolvers("tun2", dnsAddr);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
}
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
#if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
IpcClient::Interface()->deleteTun("tun2");
IpcClient::Interface()->StartRoutingIpv6();
IpcClient::Interface()->clearSavedRoutes();
}
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
IpcClient::Interface()->deleteTun("tun2");
IpcClient::Interface()->StartRoutingIpv6();
IpcClient::Interface()->clearSavedRoutes();
}
#endif
});
});
return ErrorCode::NoError;
}
@@ -166,14 +177,14 @@ void XrayProtocol::stop()
IpcClient::Interface()->StartRoutingIpv6();
#endif
qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
m_xrayProcess.kill();
m_xrayProcess.waitForFinished(3000);
m_xrayProcess.terminate();
if (m_t2sProcess) {
m_t2sProcess->stop();
}
setConnectionState(Vpn::ConnectionState::Disconnected);
#ifdef Q_OS_WIN
Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
#endif
}
QString XrayProtocol::xrayExecPath()
@@ -196,7 +207,7 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt();
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
}

View File

@@ -1,16 +1,14 @@
#ifndef XRAYPROTOCOL_H
#define XRAYPROTOCOL_H
#include "QProcess"
#include "containers/containers_defs.h"
#include "openvpnprotocol.h"
#include "settings.h"
#include "QProcess"
#include "containers/containers_defs.h"
class XrayProtocol : public VpnProtocol
{
public:
XrayProtocol(const QJsonObject &configuration, QObject *parent = nullptr);
XrayProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~XrayProtocol() override;
ErrorCode start() override;
@@ -26,12 +24,11 @@ protected:
private:
static QString xrayExecPath();
static QString tun2SocksExecPath();
private:
int m_localPort;
QString m_remoteHost;
QString m_remoteAddress;
Settings::RouteMode m_routeMode;
int m_routeMode;
QJsonObject m_configData;
QString m_primaryDNS;
QString m_secondaryDNS;
@@ -40,6 +37,7 @@ private:
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
#endif
QTemporaryFile m_xrayCfgFile;
};
#endif // XRAYPROTOCOL_H

View File

@@ -21,7 +21,6 @@
<file>images/controls/edit-3.svg</file>
<file>images/controls/eye-off.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-cog-2.svg</file>
<file>images/controls/folder-open.svg</file>
@@ -117,7 +116,6 @@
<file>server_scripts/xray/run_container.sh</file>
<file>server_scripts/xray/start.sh</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/ConnectionTypeSelectionDrawer.qml</file>
<file>ui/qml/Components/HomeContainersListView.qml</file>

View File

@@ -15,12 +15,6 @@
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)
: QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" })
{
@@ -55,7 +49,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
// check if value is not encrypted, v. < 2.0.x
retVal = m_settings.value(key);
if (retVal.isValid()) {
if (retVal.userType() == QMetaType::QByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) {
if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) {
if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";

View File

@@ -8,6 +8,10 @@
#include "keychain.h"
constexpr const char *settingsKeyTag = "settingsKeyTag";
constexpr const char *settingsIvTag = "settingsIvTag";
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
class SecureQSettings : public QObject
{
Q_OBJECT
@@ -40,7 +44,7 @@ public:
private:
QSettings m_settings;
mutable QHash<QString, QVariant> m_cache;
mutable QMap<QString, QVariant> m_cache;
QStringList encryptedKeys; // encode only key listed here
// only this fields need for backup

View File

@@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
cat > /opt/amnezia/awg/wg0.conf <<EOF
[Interface]
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
ListenPort = $AWG_SERVER_PORT
Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE

View File

@@ -17,12 +17,12 @@ iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_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 -i wg0 -o eth0 -s $WIREGUARD_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 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $WIREGUARD_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
tail -f /dev/null

View File

@@ -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";\
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 pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
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";\
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";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
@@ -12,9 +12,6 @@ if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
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 \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\

View File

@@ -33,14 +33,14 @@ conn shared
right=%any
encapsulation=yes
authby=secret
pfs=yes
pfs=no
rekey=no
keyingtries=5
dpddelay=30
dpdtimeout=120
dpdaction=clear
ikev2=never
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp2048,aes128-sha1;modp2048
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2;modp1024,aes128-sha1;modp1024
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
ikelifetime=24h
salifetime=24h
@@ -244,9 +244,9 @@ conn ikev2-cp
auto=add
ikev2=insist
rekey=no
pfs=yes
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
pfs=no
ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1
phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes128-sha2,aes256-sha2
ikelifetime=24h
salifetime=24h
encapsulation=yes

View File

@@ -538,13 +538,3 @@ void Settings::toggleDevGatewayEnv(bool enabled)
{
m_isDevGatewayEnv = enabled;
}
bool Settings::isHomeAdLabelVisible()
{
return value("Conf/homeAdLabelVisible", true).toBool();
}
void Settings::disableHomeAdLabel()
{
setValue("Conf/homeAdLabelVisible", false);
}

View File

@@ -222,9 +222,6 @@ public:
bool isDevGatewayEnv();
void toggleDevGatewayEnv(bool enabled);
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
signals:
void saveLogsChanged(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

View File

@@ -10,15 +10,16 @@ FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
m_focusChain {},
m_focusedItem { nullptr },
m_rootObjects {},
m_defaultFocusItem { nullptr },
m_defaultFocusItem { QSharedPointer<QQuickItem>() },
m_lvfc { nullptr }
{
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url) {
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
m_defaultFocusItem = newDefaultFocusItem;
}
});
QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this,
[this](QObject *object, const QUrl &url) {
QQuickItem *newDefaultFocusItem = object->findChild<QQuickItem *>("defaultFocusItem");
if (newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) {
m_defaultFocusItem.reset(newDefaultFocusItem);
}
});
QObject::connect(this, &FocusController::focusedItemChanged, this,
[this]() { m_focusedItem->forceActiveFocus(Qt::TabFocusReason); });
@@ -64,7 +65,7 @@ void FocusController::setFocusItem(QQuickItem *item)
void FocusController::setFocusOnDefaultItem()
{
setFocusItem(m_defaultFocusItem);
setFocusItem(m_defaultFocusItem.get());
}
void FocusController::pushRootObject(QObject *object)

View File

@@ -42,11 +42,11 @@ private:
void focusPreviousListViewItem();
void dropListView();
QQmlApplicationEngine *m_engine; // Pointer to engine to get root object
QList<QObject *> m_focusChain; // List of current objects to be focused
QQuickItem *m_focusedItem; // Pointer to the active focus item
QStack<QObject *> m_rootObjects; // Pointer to stack of roots for focus chain
QQuickItem *m_defaultFocusItem;
QSharedPointer<QQmlApplicationEngine> m_engine; // Pointer to engine to get root object
QList<QObject *> m_focusChain; // List of current objects to be focused
QQuickItem *m_focusedItem; // Pointer to the active focus item
QStack<QObject *> m_rootObjects;
QSharedPointer<QQuickItem> m_defaultFocusItem;
ListViewFocusController *m_lvfc; // ListView focus manager

View File

@@ -320,15 +320,4 @@ bool SettingsController::isOnTv()
#else
return false;
#endif
}
bool SettingsController::isHomeAdLabelVisible()
{
return m_settings->isHomeAdLabelVisible();
}
void SettingsController::disableHomeAdLabel()
{
m_settings->disableHomeAdLabel();
emit isHomeAdLabelVisibleChanged(false);
}
}

View File

@@ -29,8 +29,6 @@ public:
Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged)
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
public slots:
void toggleAmneziaDns(bool enable);
bool isAmneziaDnsEnabled();
@@ -91,9 +89,6 @@ public slots:
bool isOnTv();
bool isHomeAdLabelVisible();
void disableHomeAdLabel();
signals:
void primaryDnsChanged();
void secondaryDnsChanged();
@@ -117,8 +112,6 @@ signals:
void gatewayEndpointChanged(const QString &endpoint);
void devGatewayEnvChanged(bool enabled);
void isHomeAdLabelVisibleChanged(bool visible);
private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;

View File

@@ -65,29 +65,28 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
"Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic.")
return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s")
.arg(speed);
} else if (serviceType == serviceType::amneziaFree) {
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) {
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>");
} else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
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>");
}
return description;
}
}
case ServiceDescriptionRole: {
if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. "
"Works for any sites with no restrictions.");
return tr("Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. "
"It works for all websites, even in countries with the highest level of internet censorship.");
} else {
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.");
return tr("Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship");
}
}
case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) {
if (!isServiceAvailable) {
if (isServiceAvailable) {
return false;
}
}
@@ -144,8 +143,7 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
m_selectedServiceIndex = 0;
} else {
for (const auto &service : services) {
auto serviceObject = service.toObject();
m_services.push_back(getApiServicesData(serviceObject));
m_services.push_back(getApiServicesData(service.toObject()));
}
}
@@ -230,7 +228,7 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data)
{
auto serviceInfo = data.value(configKey::serviceInfo).toObject();
auto serviceInfo = data.value(configKey::serviceInfo).toObject();
auto serviceType = data.value(configKey::serviceType).toString();
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
auto availableCountries = data.value(configKey::availableCountries).toArray();
@@ -247,9 +245,9 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
serviceData.type = serviceType;
serviceData.protocol = serviceProtocol;
serviceData.storeEndpoint = data.value(configKey::storeEndpoint).toString();
serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();
if (data.value(configKey::isAvailable).isBool()) {
if (serviceInfo.value(configKey::isAvailable).isBool()) {
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
} else {
serviceData.isServiceAvailable = true;

View File

@@ -108,7 +108,7 @@ QString LanguageModel::getCurrentSiteUrl()
{
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
switch (language) {
case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/amnezia/amnezia.org";
case LanguageSettings::AvailableLanguageEnum::Russian: return "https://storage.googleapis.com/kldscp/amnezia.org";
default: return "https://amnezia.org";
}
}

View File

@@ -21,7 +21,6 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
}
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::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
@@ -59,7 +58,6 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
}
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::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
@@ -94,7 +92,6 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
m_serverProtocolConfig.insert(config_key::transport_proto,
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
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::junkPacketCount] =
serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
@@ -171,7 +168,6 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[SubnetAddressRole] = "subnetAddress";
roles[PortRole] = "port";
roles[ClientMtuRole] = "clientMtu";
@@ -201,7 +197,6 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
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);
serverJunkPacketCount = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
serverJunkPacketMinSize = serverProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
@@ -221,7 +216,7 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
{
if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
if (port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
|| serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
|| serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
|| serverInitPacketMagicHeader != other.serverInitPacketMagicHeader

View File

@@ -15,7 +15,6 @@ struct AwgConfig
{
AwgConfig(const QJsonObject &jsonConfig);
QString subnetAddress;
QString port;
QString clientMtu;
@@ -44,8 +43,7 @@ class AwgConfigModel : public QAbstractListModel
public:
enum Roles {
SubnetAddressRole = Qt::UserRole + 1,
PortRole,
PortRole = Qt::UserRole + 1,
ClientMtuRole,
ClientJunkPacketCountRole,

View File

@@ -21,7 +21,6 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
}
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::ClientMtuRole: m_clientProtocolConfig.insert(config_key::mtu, value.toString()); break;
}
@@ -37,7 +36,6 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
}
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::ClientMtuRole: return m_clientProtocolConfig.value(config_key::mtu);
}
@@ -58,7 +56,6 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
m_serverProtocolConfig.insert(config_key::transport_proto,
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
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);
auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
@@ -99,7 +96,6 @@ QHash<int, QByteArray> WireGuardConfigModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[SubnetAddressRole] = "subnetAddress";
roles[PortRole] = "port";
roles[ClientMtuRole] = "clientMtu";
@@ -112,13 +108,12 @@ WgConfig::WgConfig(const QJsonObject &serverProtocolConfig)
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
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);
}
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
{
if (subnetAddress != other.subnetAddress || port != other.port) {
if (port != other.port) {
return false;
}
return true;

View File

@@ -10,7 +10,6 @@ struct WgConfig
{
WgConfig(const QJsonObject &jsonConfig);
QString subnetAddress;
QString port;
QString clientMtu;
@@ -25,8 +24,7 @@ class WireGuardConfigModel : public QAbstractListModel
public:
enum Roles {
SubnetAddressRole = Qt::UserRole + 1,
PortRole,
PortRole = Qt::UserRole + 1,
ClientMtuRole
};

View File

@@ -1,73 +0,0 @@
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()
}
}
}
}

View File

@@ -135,7 +135,7 @@ DrawerType2 {
backgroundColor: AmneziaStyle.color.slateGray
textField.placeholderText: qsTr("application name")
textFieldPlaceholderText: qsTr("application name")
}
BasicButtonType {

View File

@@ -12,7 +12,7 @@ Item {
readonly property string drawerExpandedStateName: "expanded"
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)
property Component collapsedStateContent
@@ -123,7 +123,7 @@ Item {
id: background
anchors.fill: parent
color: root.isCollapsedStateActive() ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
color: root.isCollapsed ? AmneziaStyle.color.transparent : AmneziaStyle.color.translucentMidnightBlack
Behavior on color {
PropertyAnimation { duration: 200 }

View File

@@ -216,7 +216,9 @@ Item {
ColumnLayout {
id: header
anchors.fill: parent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
BackButtonType {
@@ -224,21 +226,31 @@ Item {
backButtonImage: root.headerBackButtonImage
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 {
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
Layout.fillWidth: true
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
headerText: root.headerText
width: parent.width
}
Loader {
id: listViewLoader
sourceComponent: root.listView
Layout.fillHeight: true
}
}
}

View File

@@ -30,8 +30,6 @@ ListView {
property bool isFocusable: true
ScrollBar.vertical: ScrollBarType {}
ButtonGroup {
id: buttonGroup
}

View File

@@ -20,6 +20,7 @@ Item {
id: timer
interval: 200 // Milliseconds
onTriggered: {
console.debug(">>> PageType timer triggered")
FocusController.resetRootObject()
FocusController.setFocusOnDefaultItem()
}

View File

@@ -22,9 +22,11 @@ Item {
property var clickedFunc
property alias textField: textField
property alias textFieldText: textField.text
property string textFieldTextColor: AmneziaStyle.color.paleGray
property string textFieldTextDisabledColor: AmneziaStyle.color.mutedGray
property string textFieldPlaceholderText
property bool textFieldEditable: true
property string borderColor: AmneziaStyle.color.slateGray
@@ -99,6 +101,7 @@ Item {
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
placeholderText: root.textFieldPlaceholderText
placeholderTextColor: AmneziaStyle.color.charcoalGray
selectionColor: AmneziaStyle.color.richBrown
@@ -126,8 +129,8 @@ Item {
}
onActiveFocusChanged: {
if (root.checkEmptyText && text === "") {
root.errorText = qsTr("The field can't be empty")
if (checkEmptyText && textFieldText === "") {
errorText = qsTr("The field can't be empty")
}
}

View File

@@ -26,6 +26,5 @@ QtObject {
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 cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
readonly property color pearlGray: '#EAEAEC'
}
}

View File

@@ -66,18 +66,18 @@ PageType {
Layout.leftMargin: 16
headerText: qsTr("Gateway endpoint")
textField.text: SettingsController.gatewayEndpoint
textFieldText: SettingsController.gatewayEndpoint
buttonImageSource: textField.text !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
buttonImageSource: textFieldText !== "" ? "qrc:/images/controls/refresh-cw.svg" : ""
clickedFunc: function() {
SettingsController.resetGatewayEndpoint()
}
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== SettingsController.gatewayEndpoint) {
SettingsController.gatewayEndpoint = textField.text
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== SettingsController.gatewayEndpoint) {
SettingsController.gatewayEndpoint = textFieldText
}
}
}

View File

@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import SortFilterProxyModel 0.2
@@ -43,18 +42,8 @@ PageType {
objectName: "homeColumnLayout"
anchors.fill: parent
anchors.topMargin: 12
anchors.bottomMargin: 16
AdLabel {
id: adLabel
Layout.fillWidth: true
Layout.preferredHeight: adLabel.contentHeight
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 22
}
anchors.topMargin: 34
anchors.bottomMargin: 34
BasicButtonType {
id: loggingButton
@@ -97,6 +86,7 @@ PageType {
objectName: "splitTunnelingButton"
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.bottomMargin: 34
leftPadding: 16
rightPadding: 16
@@ -266,11 +256,11 @@ PageType {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
Layout.bottomMargin: drawer.isCollapsed ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
spacing: 0
BasicButtonType {
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsedStateActive
enabled: (ServersModel.defaultServerImagePathCollapsed !== "") && drawer.isCollapsed
hoverEnabled: enabled
implicitHeight: 36
@@ -288,9 +278,8 @@ PageType {
buttonTextLabel.font.pixelSize: 13
buttonTextLabel.font.weight: 400
text: drawer.isCollapsedStateActive ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
text: drawer.isCollapsed ? ServersModel.defaultServerDescriptionCollapsed : ServersModel.defaultServerDescriptionExpanded
leftImageSource: ServersModel.defaultServerImagePathCollapsed
leftImageColor: ""
changeLeftImageSize: false
rightImageSource: hoverEnabled ? "qrc:/images/controls/chevron-down.svg" : ""
@@ -348,6 +337,7 @@ PageType {
objectName: "containersListView"
rootWidth: root.width
height: 500 // TODO: make calculated
Connections {
objectName: "rowLayoutConnections"

View File

@@ -103,12 +103,12 @@ PageType {
Layout.topMargin: 40
headerText: qsTr("MTU")
textField.text: clientMtu
textFieldText: clientMtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== clientMtu) {
clientMtu = textField.text
if (textFieldText !== clientMtu) {
clientMtu = textFieldText
}
}
checkEmptyText: true
@@ -121,12 +121,12 @@ PageType {
Layout.topMargin: 16
headerText: "Jc - Junk packet count"
textField.text: clientJunkPacketCount
textFieldText: clientJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketCount) {
clientJunkPacketCount = textField.text
if (textFieldText !== clientJunkPacketCount) {
clientJunkPacketCount = textFieldText
}
}
@@ -141,12 +141,12 @@ PageType {
Layout.topMargin: 16
headerText: "Jmin - Junk packet minimum size"
textField.text: clientJunkPacketMinSize
textFieldText: clientJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textField.text
if (textFieldText !== clientJunkPacketMinSize) {
clientJunkPacketMinSize = textFieldText
}
}
@@ -161,12 +161,12 @@ PageType {
Layout.topMargin: 16
headerText: "Jmax - Junk packet maximum size"
textField.text: clientJunkPacketMaxSize
textFieldText: clientJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textField.text
if (textFieldText !== clientJunkPacketMaxSize) {
clientJunkPacketMaxSize = textFieldText
}
}
@@ -189,7 +189,7 @@ PageType {
enabled: false
headerText: qsTr("Port")
textField.text: port
textFieldText: port
}
TextFieldWithHeaderType {
@@ -200,7 +200,7 @@ PageType {
enabled: false
headerText: "S1 - Init packet junk size"
textField.text: serverInitPacketJunkSize
textFieldText: serverInitPacketJunkSize
}
TextFieldWithHeaderType {
@@ -211,7 +211,7 @@ PageType {
enabled: false
headerText: "S2 - Response packet junk size"
textField.text: serverResponsePacketJunkSize
textFieldText: serverResponsePacketJunkSize
}
TextFieldWithHeaderType {
@@ -222,7 +222,7 @@ PageType {
enabled: false
headerText: "H1 - Init packet magic header"
textField.text: serverInitPacketMagicHeader
textFieldText: serverInitPacketMagicHeader
}
TextFieldWithHeaderType {
@@ -233,7 +233,7 @@ PageType {
enabled: false
headerText: "H2 - Response packet magic header"
textField.text: serverResponsePacketMagicHeader
textFieldText: serverResponsePacketMagicHeader
}
TextFieldWithHeaderType {
@@ -244,7 +244,7 @@ PageType {
enabled: false
headerText: "H3 - Underload packet magic header"
textField.text: serverUnderloadPacketMagicHeader
textFieldText: serverUnderloadPacketMagicHeader
}
TextFieldWithHeaderType {
@@ -255,7 +255,7 @@ PageType {
enabled: false
headerText: "H4 - Transport packet magic header"
textField.text: serverTransportPacketMagicHeader
textFieldText: serverTransportPacketMagicHeader
}
}
}

View File

@@ -36,13 +36,13 @@ PageType {
ListView {
id: listview
property bool isFocusable: true
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
width: parent.width
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
@@ -76,7 +76,7 @@ PageType {
implicitWidth: listview.width
implicitHeight: col.implicitHeight
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
property alias portTextField: portTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
ColumnLayout {
@@ -98,19 +98,20 @@ PageType {
}
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
if (textFieldText !== port) {
port = textFieldText
}
}
@@ -118,23 +119,22 @@ PageType {
}
TextFieldWithHeaderType {
id: portTextField
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
@@ -144,16 +144,16 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jc - Junk packet count")
textField.text: serverJunkPacketCount
textFieldText: serverJunkPacketCount
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text === "") {
textField.text = "0"
if (textFieldText === "") {
textFieldText = "0"
}
if (textField.text !== serverJunkPacketCount) {
serverJunkPacketCount = textField.text
if (textFieldText !== serverJunkPacketCount) {
serverJunkPacketCount = textFieldText
}
}
@@ -166,12 +166,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jmin - Junk packet minimum size")
textField.text: serverJunkPacketMinSize
textFieldText: serverJunkPacketMinSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textField.text
if (textFieldText !== serverJunkPacketMinSize) {
serverJunkPacketMinSize = textFieldText
}
}
@@ -184,12 +184,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Jmax - Junk packet maximum size")
textField.text: serverJunkPacketMaxSize
textFieldText: serverJunkPacketMaxSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textField.text
if (textFieldText !== serverJunkPacketMaxSize) {
serverJunkPacketMaxSize = textFieldText
}
}
@@ -202,12 +202,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("S1 - Init packet junk size")
textField.text: serverInitPacketJunkSize
textFieldText: serverInitPacketJunkSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textField.text
if (textFieldText !== serverInitPacketJunkSize) {
serverInitPacketJunkSize = textFieldText
}
}
@@ -226,12 +226,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("S2 - Response packet junk size")
textField.text: serverResponsePacketJunkSize
textFieldText: serverResponsePacketJunkSize
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textField.text
if (textFieldText !== serverResponsePacketJunkSize) {
serverResponsePacketJunkSize = textFieldText
}
}
@@ -250,12 +250,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H1 - Init packet magic header")
textField.text: serverInitPacketMagicHeader
textFieldText: serverInitPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textField.text
if (textFieldText !== serverInitPacketMagicHeader) {
serverInitPacketMagicHeader = textFieldText
}
}
@@ -268,12 +268,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H2 - Response packet magic header")
textField.text: serverResponsePacketMagicHeader
textFieldText: serverResponsePacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textField.text
if (textFieldText !== serverResponsePacketMagicHeader) {
serverResponsePacketMagicHeader = textFieldText
}
}
@@ -286,12 +286,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H4 - Transport packet magic header")
textField.text: serverTransportPacketMagicHeader
textFieldText: serverTransportPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textField.text
if (textFieldText !== serverTransportPacketMagicHeader) {
serverTransportPacketMagicHeader = textFieldText
}
}
@@ -304,12 +304,12 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("H3 - Underload packet magic header")
textField.text: serverUnderloadPacketMagicHeader
textFieldText: serverUnderloadPacketMagicHeader
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textField.text
if (textFieldText !== serverUnderloadPacketMagicHeader) {
serverUnderloadPacketMagicHeader = textFieldText
}
}
@@ -332,8 +332,7 @@ PageType {
junkPacketMaxSizeTextField.errorText === "" &&
junkPacketMinSizeTextField.errorText === "" &&
junkPacketCountTextField.errorText === "" &&
portTextField.errorText === "" &&
vpnAddressSubnetTextField.errorText === ""
portTextField.errorText === ""
text: qsTr("Save")

View File

@@ -89,18 +89,18 @@ PageType {
Layout.topMargin: 32
headerText: qsTr("Disguised as traffic from")
textField.text: site
textFieldText: site
textField.onEditingFinished: {
if (textField.text !== site) {
var tmpText = textField.text
if (textFieldText !== site) {
var tmpText = textFieldText
tmpText = tmpText.toLocaleLowerCase()
var indexHttps = tmpText.indexOf("https://")
if (indexHttps === 0) {
tmpText = textField.text.substring(8)
tmpText = textFieldText.substring(8)
} else {
site = textField.text
site = textFieldText
}
}
}
@@ -113,13 +113,13 @@ PageType {
Layout.topMargin: 16
headerText: qsTr("Port")
textField.text: port
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
if (textFieldText !== port) {
port = textFieldText
}
}
}

View File

@@ -88,13 +88,13 @@ PageType {
Layout.topMargin: 32
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
textFieldText: subnetAddress
parentFlickable: fl
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
if (textFieldText !== subnetAddress) {
subnetAddress = textFieldText
}
}
}
@@ -137,13 +137,13 @@ PageType {
enabled: isPortEditable
headerText: qsTr("Port")
textField.text: port
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
if (textFieldText !== port) {
port = textFieldText
}
}
}
@@ -176,6 +176,7 @@ PageType {
headerText: qsTr("Hash")
drawerParent: root
parentFlickable: fl
listView: ListViewWithRadioButtonType {
id: hashListView
@@ -224,6 +225,7 @@ PageType {
headerText: qsTr("Cipher")
drawerParent: root
parentFlickable: fl
listView: ListViewWithRadioButtonType {
id: cipherListView

View File

@@ -93,13 +93,13 @@ PageType {
enabled: isPortEditable
headerText: qsTr("Port")
textField.text: port
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
if (textFieldText !== port) {
port = textFieldText
}
}
}

View File

@@ -16,6 +16,8 @@ import "../Components"
PageType {
id: root
defaultActiveFocusItem: listview.currentItem.mtuTextField.textField
Item {
id: focusItem
onFocusChanged: {
@@ -97,12 +99,12 @@ PageType {
Layout.topMargin: 40
headerText: qsTr("MTU")
textField.text: clientMtu
textFieldText: clientMtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== clientMtu) {
clientMtu = textField.text
if (textFieldText !== clientMtu) {
clientMtu = textFieldText
}
}
checkEmptyText: true
@@ -124,7 +126,7 @@ PageType {
enabled: false
headerText: qsTr("Port")
textField.text: port
textFieldText: port
}
}
}

View File

@@ -59,7 +59,7 @@ PageType {
delegate: Item {
id: delegateItem
property alias focusItemId: vpnAddressSubnetTextField
property alias focusItemId: portTextField.textField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
implicitWidth: listview.width
@@ -83,18 +83,20 @@ PageType {
}
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
headerText: qsTr("Port")
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
if (textFieldText !== port) {
port = textFieldText
}
}
@@ -102,23 +104,22 @@ PageType {
}
TextFieldWithHeaderType {
id: portTextField
id: mtuTextField
Layout.fillWidth: true
Layout.topMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
headerText: qsTr("MTU")
textFieldText: mtu
textField.validator: IntValidator { bottom: 576; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
if (textFieldText === "") {
textFieldText = "0"
}
if (textFieldText !== mtu) {
mtu = textFieldText
}
}
checkEmptyText: true
}
@@ -128,8 +129,7 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: portTextField.errorText === "" &&
vpnAddressSubnetTextField.errorText === ""
enabled: portTextField.errorText === ""
text: qsTr("Save")

View File

@@ -86,18 +86,18 @@ PageType {
Layout.topMargin: 32
headerText: qsTr("Disguised as traffic from")
textField.text: site
textFieldText: site
textField.onEditingFinished: {
if (textField.text !== site) {
var tmpText = textField.text
if (textFieldText !== site) {
var tmpText = textFieldText
tmpText = tmpText.toLocaleLowerCase()
var indexHttps = tmpText.indexOf("https://")
if (indexHttps === 0) {
tmpText = textField.text.substring(8)
tmpText = textFieldText.substring(8)
} else {
site = textField.text
site = textFieldText
}
}
}

View File

@@ -211,9 +211,9 @@ PageType {
port = tempPort
username = tempUsername
password = tempPassword
portTextField.textField.text = port
usernameTextField.textField.text = username
passwordTextField.textField.text = password
portTextField.textFieldText = port
usernameTextField.textFieldText = username
passwordTextField.textFieldText = password
}
}
@@ -231,14 +231,14 @@ PageType {
parentFlickable: fl
headerText: qsTr("Port")
textField.text: port
textFieldText: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== port) {
port = textField.text
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== port) {
port = textFieldText
}
}
}
@@ -251,14 +251,14 @@ PageType {
parentFlickable: fl
headerText: qsTr("Username")
textField.placeholderText: "username"
textField.text: username
textFieldPlaceholderText: "username"
textFieldText: username
textField.maximumLength: 32
textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== username) {
username = textField.text
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== username) {
username = textFieldText
}
}
}
@@ -273,12 +273,12 @@ PageType {
parentFlickable: fl
headerText: qsTr("Password")
textField.placeholderText: "password"
textField.text: password
textFieldPlaceholderText: "password"
textFieldText: password
textField.maximumLength: 32
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textField.text !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: ""
clickedFunc: function() {
@@ -286,9 +286,9 @@ PageType {
}
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== password) {
password = textField.text
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
if (textFieldText !== password) {
password = textFieldText
}
}
}
@@ -309,19 +309,19 @@ PageType {
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
return
}
if (usernameTextField.textField.text && passwordTextField.textField.text === "") {
if (usernameTextField.textFieldText && passwordTextField.textFieldText === "") {
passwordTextField.errorText = qsTr("Password cannot be empty")
return
} else if (usernameTextField.textField.text === "" && passwordTextField.textField.text) {
} else if (usernameTextField.textFieldText === "" && passwordTextField.textFieldText) {
usernameTextField.errorText = qsTr("Username cannot be empty")
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(Socks5ProxyConfigModel.getConfig())
tempPort = portTextField.textField.text
tempUsername = usernameTextField.textField.text
tempPassword = passwordTextField.textField.text
tempPort = portTextField.textFieldText
tempUsername = usernameTextField.textFieldText
tempPassword = passwordTextField.textFieldText
changeSettingsDrawer.closeTriggered()
}
}

View File

@@ -87,6 +87,7 @@ PageType {
LabelWithButtonType {
id: backup
visible: !SettingsController.isOnTv()
Layout.fillWidth: true
text: qsTr("Backup")
@@ -98,7 +99,9 @@ PageType {
}
}
DividerType {}
DividerType {
visible: !SettingsController.isOnTv()
}
LabelWithButtonType {
id: about

View File

@@ -18,13 +18,13 @@ PageType {
ListView {
id: menuContent
property bool isFocusable: true
property var selectedText
width: parent.width
height: parent.height
height: menuContent.contentItem.height
clip: true
interactive: true
interactive: false
model: ApiCountryModel
ButtonGroup {
@@ -34,8 +34,8 @@ PageType {
delegate: ColumnLayout {
id: content
width: menuContent.width
height: content.implicitHeight
implicitWidth: parent.width
implicitHeight: content.implicitHeight
RowLayout {
VerticalRadioButton {

View File

@@ -15,100 +15,61 @@ import "../Components"
PageType {
id: root
property list<QtObject> labelsModel: [
regionObject,
priceObject,
endDateObject,
speedObject
]
FlickableType {
id: fl
anchors.top: parent.top
anchors.bottom: parent.bottom
contentHeight: content.height
QtObject {
id: regionObject
ColumnLayout {
id: content
readonly property string title: qsTr("For the region")
readonly property string contentKey: "region"
readonly property string objectImageSource: "qrc:/images/controls/map-pin.svg"
}
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
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
LabelWithImageType {
Layout.fillWidth: true
Layout.margins: 16
imageSource: objectImageSource
leftText: title
rightText: ApiServicesModel.getSelectedServiceData(contentKey)
imageSource: "qrc:/images/controls/map-pin.svg"
leftText: qsTr("For the region")
rightText: ApiServicesModel.getSelectedServiceData("region")
}
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 !== ""
}
}
footer: ColumnLayout {
width: listView.width
spacing: 0
LabelWithImageType {
Layout.fillWidth: true
Layout.margins: 16
imageSource: "qrc:/images/controls/gauge.svg"
leftText: qsTr("Speed")
rightText: ApiServicesModel.getSelectedServiceData("speed")
}
ParagraphTextType {
Layout.fillWidth: true
@@ -127,8 +88,6 @@ PageType {
return text.replace("%1", LanguageModel.getCurrentSiteUrl())
}
visible: text !== ""
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton

View File

@@ -252,7 +252,7 @@ PageType {
Layout.fillWidth: true
textField.placeholderText: qsTr("application name")
textFieldPlaceholderText: qsTr("application name")
buttonImageSource: "qrc:/images/controls/plus.svg"
rightButtonClickedOnEnter: true

View File

@@ -221,8 +221,15 @@ PageType {
SettingsController.clearSettings()
PageController.goToPageHome()
}
if (!GC.isMobile()) {
// root.defaultActiveFocusItem.forceActiveFocus()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
// root.defaultActiveFocusItem.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)

View File

@@ -67,7 +67,7 @@ PageType {
Layout.fillWidth: true
headerText: qsTr("Primary DNS")
textField.text: SettingsController.primaryDns
textFieldText: SettingsController.primaryDns
textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp()
}
@@ -79,7 +79,7 @@ PageType {
Layout.fillWidth: true
headerText: qsTr("Secondary DNS")
textField.text: SettingsController.secondaryDns
textFieldText: SettingsController.secondaryDns
textField.validator: RegularExpressionValidator {
regularExpression: InstallController.ipAddressRegExp()
}
@@ -105,12 +105,19 @@ PageType {
var yesButtonFunction = function() {
SettingsController.primaryDns = "1.1.1.1"
primaryDns.textField.text = SettingsController.primaryDns
primaryDns.textFieldText = SettingsController.primaryDns
SettingsController.secondaryDns = "1.0.0.1"
secondaryDns.textField.text = SettingsController.secondaryDns
secondaryDns.textFieldText = SettingsController.secondaryDns
PageController.showNotificationMessage(qsTr("Settings have been reset"))
if (!GC.isMobile()) {
// defaultActiveFocusItem.forceActiveFocus()
}
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
// defaultActiveFocusItem.forceActiveFocus()
}
}
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
@@ -125,11 +132,11 @@ PageType {
text: qsTr("Save")
clickedFunc: function() {
if (primaryDns.textField.text !== SettingsController.primaryDns) {
SettingsController.primaryDns = primaryDns.textField.text
if (primaryDns.textFieldText !== SettingsController.primaryDns) {
SettingsController.primaryDns = primaryDns.textFieldText
}
if (secondaryDns.textField.text !== SettingsController.secondaryDns) {
SettingsController.secondaryDns = secondaryDns.textField.text
if (secondaryDns.textFieldText !== SettingsController.secondaryDns) {
SettingsController.secondaryDns = secondaryDns.textFieldText
}
PageController.showNotificationMessage(qsTr("Settings saved"))
}

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