Compare commits

..

8 Commits

Author SHA1 Message Date
NickVs2015
9a472c6793 fix: bad rule xargs, cgroup v2 mkdir, amnvpnrt rt_tables, restart flush 2026-08-06 19:33:37 +03:00
NickVs2015
9bf974a2f4 fix: amnvpnrt flush error 2026-08-06 16:42:19 +03:00
NickVs2015
46100fd311 fix: make Linux DNS retry setup 2026-08-06 15:16:29 +03:00
NickVs2015
34b62f8fd6 fix: replace m_linkDefaultRoutes map with single m_gatewayIfindex
fix: solve infinite reconnect NM down
2026-08-06 14:46:01 +03:00
NickVs2015
b8e9ec8570 fix: killswitch compatible 2026-08-06 14:01:24 +03:00
NickVs2015
c35988d79f fix: NM down/up reconnection problem 2026-08-06 14:01:24 +03:00
NickVs2015
410538c6b2 fix: dns rewrites and flush 2026-08-06 14:01:24 +03:00
NickVs2015
cc71330c27 fix: make dbus async 2026-08-06 14:01:24 +03:00
38 changed files with 4915 additions and 890 deletions

View File

@@ -745,37 +745,11 @@ jobs:
needs: Bake-Prebuilts-Android
if: ${{ always() }}
strategy:
fail-fast: false
matrix:
include:
- label: 'android9-10'
qt_version: '6.10.1'
min_sdk: 28
max_sdk: 29
version_code_offset: 0
build_aab: true
- label: 'android11+'
qt_version: '6.10.3'
min_sdk: 30
max_sdk: ''
version_code_offset: 1
build_aab: true
- label: 'android9-10-qt610-3'
qt_version: '6.10.3'
min_sdk: 28
max_sdk: 29
version_code_offset: 0
build_aab: false
env:
ANDROID_PLATFORM: android-${{ matrix.min_sdk }}
ANDROID_PLATFORM: android-28
NDK_VERSION: 27.0.11718014
QT_VERSION: ${{ matrix.qt_version }}
QT_VERSION: 6.10.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
APP_ANDROID_MIN_SDK: ${{ matrix.min_sdk }}
APP_ANDROID_MAX_SDK: ${{ matrix.max_sdk }}
APP_ANDROID_VERSION_CODE_OFFSET: ${{ matrix.version_code_offset }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
@@ -883,82 +857,62 @@ jobs:
QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
LABEL: ${{ matrix.label }}
BUILD_AAB: ${{ matrix.build_aab }}
shell: bash
run: |
OUT=deploy/artifacts
rm -rf $OUT && mkdir -p $OUT
if [[ "$BUILD_AAB" == "true" ]]; then
deploy/build.sh -t android --sign --aab
else
deploy/build.sh -t android --sign
fi
deploy/build.sh -t android --sign --aab
VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC deploy/build/CMakeCache.txt | cut -d= -f2)
mv deploy/build/client/android-build/AmneziaVPN.apk \
$OUT/AmneziaVPN_${VERSION}_${LABEL}_universal.apk
if [[ "$BUILD_AAB" == "true" ]]; then
mv deploy/build/client/android-build/build/outputs/bundle/release/android-build-release.aab \
$OUT/AmneziaVPN_${VERSION}_${LABEL}.aab
fi
(cd deploy/build/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_universal.apk)
(cd deploy/build/client/android-build/build/outputs/bundle/release && mv android-build-release.aab AmneziaVPN_${VERSION}.aab)
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
deploy/build.sh -t android --sign --abi ${abi} --build ./deploy/build/${abi}
mv deploy/build/${abi}/client/android-build/AmneziaVPN.apk \
$OUT/AmneziaVPN_${VERSION}_${LABEL}_${abi}.apk
(cd deploy/build/${abi}/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_${abi}.apk)
done
ls -l $OUT
- name: 'Upload universal APK'
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*_universal.apk
path: deploy/build/client/android-build/*.apk
archive: false
retention-days: 7
- name: 'Upload AAB'
uses: actions/upload-artifact@v7
with:
path: deploy/build/client/android-build/build/outputs/bundle/release/*.aab
archive: false
retention-days: 7
- name: 'Upload arm64-v8a APK'
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*_arm64-v8a.apk
path: deploy/build/arm64-v8a/client/android-build/*.apk
archive: false
retention-days: 7
- name: 'Upload armeabi-v7a APK'
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*_armeabi-v7a.apk
path: deploy/build/armeabi-v7a/client/android-build/*.apk
archive: false
retention-days: 7
- name: 'Upload x86 APK'
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*_x86.apk
path: deploy/build/x86/client/android-build/*.apk
archive: false
retention-days: 7
- name: 'Upload x86_64 APK'
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*_x86_64.apk
path: deploy/build/x86_64/client/android-build/*.apk
archive: false
retention-days: 7
- name: 'Upload AAB'
if: ${{ matrix.build_aab }}
uses: actions/upload-artifact@v7
with:
path: deploy/artifacts/*.aab
archive: false
retention-days: 7
# ------------------------------------------------------
Extra:
runs-on: ubuntu-latest
steps:

View File

@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 5.0.1.1)
set(AMNEZIAVPN_VERSION 5.0.1.0)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,12 +28,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})
# bump by 2 on every release, because we're releasing two versions on the play store
set(APP_ANDROID_VERSION_CODE 2144)
if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET)
math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}")
endif()
set(APP_ANDROID_VERSION_CODE 2141)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,8 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
if(NOT DEFINED APP_ANDROID_MIN_SDK)
set(APP_ANDROID_MIN_SDK 28)
endif()
set(APP_ANDROID_MIN_SDK 28)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
@@ -15,6 +13,7 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE_BUILD_TYPE")
@@ -54,27 +53,3 @@ file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/androi
find_package(openvpn-pt-android REQUIRED)
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
set(APP_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
if(APP_ANDROID_MAX_SDK)
set(APP_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/android-package-source)
file(REMOVE_RECURSE ${APP_ANDROID_PACKAGE_SOURCE_DIR})
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/android/ DESTINATION ${APP_ANDROID_PACKAGE_SOURCE_DIR})
set(manifest_path ${APP_ANDROID_PACKAGE_SOURCE_DIR}/AndroidManifest.xml)
set(manifest_anchor "android:installLocation=\"auto\">")
file(READ ${manifest_path} manifest_contents)
string(REPLACE
"${manifest_anchor}"
"${manifest_anchor}\n\n <uses-sdk android:maxSdkVersion=\"${APP_ANDROID_MAX_SDK}\" />"
patched_contents "${manifest_contents}")
if(patched_contents STREQUAL manifest_contents)
message(FATAL_ERROR
"Failed to set maxSdkVersion=${APP_ANDROID_MAX_SDK}: anchor '${manifest_anchor}' "
"not found in ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml")
endif()
file(WRITE ${manifest_path} "${patched_contents}")
endif()
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${APP_ANDROID_PACKAGE_SOURCE_DIR})

View File

@@ -64,7 +64,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
${CLIENT_ROOT_DIR}/core/utils/utilities.h
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
@@ -148,7 +147,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.cpp
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp

View File

@@ -2,8 +2,6 @@
#include "core/controllers/gatewayController.h"
#include "core/repositories/secureServersRepository.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/api/gatewayPayloadBuilder.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include <QtConcurrent/QtConcurrent>
@@ -83,12 +81,15 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
// both country codes and service types are arrays here, one entry per gateway stack the user has
const QJsonObject payload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::locale, apiUtils::getAppLanguageCode(m_appSettingsRepository))
.addField(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode))
.addField(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType))
.build();
QJsonObject payload;
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
if (services.contains(apiDefs::key::userCountryCode)) {
payload.insert(apiDefs::key::userCountryCode, services.value(apiDefs::key::userCountryCode));
}
if (services.contains(apiDefs::key::serviceType)) {
payload.insert(apiDefs::key::serviceType, services.value(apiDefs::key::serviceType));
}
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
return future.then([gatewayController](QPair<ErrorCode, QByteArray> result) -> QPair<ErrorCode, QJsonArray> {

View File

@@ -1,6 +1,7 @@
#include "servicesCatalogController.h"
#include <QJsonDocument>
#include <QSysInfo>
#include <QJsonArray>
#include <QEventLoop>
#include <QDebug>
@@ -10,7 +11,6 @@
#include <limits>
#include "core/controllers/gatewayController.h"
#include "core/utils/api/gatewayPayloadBuilder.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
@@ -212,9 +212,11 @@ ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository
ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData)
{
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::cliName, QString(APPLICATION_NAME))
.build();
QJsonObject apiPayload;
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);

View File

@@ -8,6 +8,7 @@
#include <QJsonObject>
#include <QPromise>
#include <QSet>
#include <QSysInfo>
#include <QUuid>
#include <QVariantMap>
@@ -20,7 +21,6 @@
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/api/gatewayPayloadBuilder.h"
#include "core/controllers/gatewayController.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
@@ -97,6 +97,39 @@ SubscriptionController::SubscriptionController(SecureServersRepository* serversR
{
}
QJsonObject SubscriptionController::GatewayRequestData::toJsonObject() const
{
QJsonObject obj;
if (!osVersion.isEmpty()) {
obj[apiDefs::key::osVersion] = osVersion;
}
if (!appVersion.isEmpty()) {
obj[apiDefs::key::appVersion] = appVersion;
}
if (!appLanguage.isEmpty()) {
obj[apiDefs::key::appLanguage] = appLanguage;
}
if (!installationUuid.isEmpty()) {
obj[apiDefs::key::uuid] = installationUuid;
}
if (!userCountryCode.isEmpty()) {
obj[apiDefs::key::userCountryCode] = userCountryCode;
}
if (!serverCountryCode.isEmpty()) {
obj[apiDefs::key::serverCountryCode] = serverCountryCode;
}
if (!serviceType.isEmpty()) {
obj[apiDefs::key::serviceType] = serviceType;
}
if (!serviceProtocol.isEmpty()) {
obj[apiDefs::key::serviceProtocol] = serviceProtocol;
}
if (!authData.isEmpty()) {
obj[apiDefs::key::authData] = authData;
}
return obj;
}
SubscriptionController::ProtocolData SubscriptionController::generateProtocolData(const QString &protocol)
{
ProtocolData protocolData;
@@ -111,15 +144,13 @@ SubscriptionController::ProtocolData SubscriptionController::generateProtocolDat
return protocolData;
}
QString SubscriptionController::publicKeyForProtocol(const QString &protocol, const ProtocolData &protocolData)
void SubscriptionController::appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload)
{
if (protocol == configKey::awg) {
return protocolData.wireGuardClientPubKey;
apiPayload[apiDefs::key::publicKey] = protocolData.wireGuardClientPubKey;
} else if (protocol == configKey::vless) {
apiPayload[apiDefs::key::publicKey] = protocolData.xrayUuid;
}
if (protocol == configKey::vless) {
return protocolData.xrayUuid;
}
return {};
}
ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
@@ -209,12 +240,18 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
const QString &serviceProtocol, const ProtocolData &protocolData,
CaptchaInfo &captchaInfo)
{
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, userCountryCode)
.addField(apiDefs::key::serviceType, serviceType)
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.build();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
userCountryCode,
"",
serviceType,
serviceProtocol,
QJsonObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
@@ -254,15 +291,20 @@ ErrorCode SubscriptionController::importTrialFromGateway(const QString &userCoun
return ErrorCode::ApiConfigEmptyError;
}
ProtocolData protocolData = generateProtocolData(serviceProtocol);
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
userCountryCode,
"",
serviceType,
serviceProtocol,
QJsonObject() };
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, userCountryCode)
.addField(apiDefs::key::serviceType, serviceType)
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.addField(apiDefs::key::email, trimmedEmail)
.build();
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload.insert(apiDefs::key::email, trimmedEmail);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody);
@@ -303,13 +345,19 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
const QString &transactionId, bool isTestPurchase,
int *duplicateServerIndex)
{
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, userCountryCode)
.addField(apiDefs::key::serviceType, serviceType)
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.addField(apiDefs::key::transactionId, transactionId)
.build();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
userCountryCode,
"",
serviceType,
serviceProtocol,
QJsonObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload[apiDefs::key::transactionId] = transactionId;
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
@@ -403,15 +451,23 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, newCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.addField(apiDefs::key::isConnectEvent, isConnectEvent ? QJsonValue(true) : QJsonValue())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
newCountryCode,
apiV2->serviceType(),
serviceProtocol,
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
if (isConnectEvent) {
apiPayload[apiDefs::key::isConnectEvent] = true;
}
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
@@ -487,17 +543,26 @@ ErrorCode SubscriptionController::resolveUpdateServiceCaptcha(const QString &ser
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString serviceProtocol = apiV2->serviceProtocol();
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, newCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.addField(apiDefs::key::captchaId, captchaId)
.addField(apiDefs::key::captchaSolution, normalizeCaptchaSolution(captchaSolution))
.addField(apiDefs::key::isConnectEvent, isConnectEvent ? QJsonValue(true) : QJsonValue())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
newCountryCode,
apiV2->serviceType(),
serviceProtocol,
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
if (isConnectEvent) {
apiPayload[apiDefs::key::isConnectEvent] = true;
}
apiPayload["captcha_id"] = captchaId;
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
@@ -530,12 +595,18 @@ ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
return ErrorCode::NoError;
}
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, apiV2->apiConfig.serverCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
apiV2->apiConfig.serverCountryCode,
apiV2->serviceType(),
"",
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QByteArray responseBody;
@@ -561,13 +632,18 @@ ErrorCode SubscriptionController::deactivateExternalDevice(const QString &server
return ErrorCode::NoError;
}
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::installationUuid, uuid)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
uuid,
apiV2->apiConfig.userCountryCode,
serverCountryCode,
apiV2->serviceType(),
"",
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QByteArray responseBody;
@@ -595,14 +671,19 @@ ErrorCode SubscriptionController::exportNativeConfig(const QString &serverId, co
QString protocol = configKey::awg;
ProtocolData protocolData = generateProtocolData(protocol);
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::serviceProtocol, protocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(protocol, protocolData))
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
serverCountryCode,
apiV2->serviceType(),
protocol,
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(protocol, protocolData, apiPayload);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase);
@@ -625,13 +706,18 @@ ErrorCode SubscriptionController::revokeNativeConfig(const QString &serverId, co
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString protocol = configKey::awg;
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serverCountryCode, serverCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::serviceProtocol, protocol)
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
serverCountryCode,
apiV2->serviceType(),
protocol,
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase);
@@ -938,13 +1024,20 @@ ErrorCode SubscriptionController::getAccountInfo(const QString &serverId, QJsonO
}
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
.addField(apiDefs::key::subscriptionStatus, getSubscriptionStatusForRenewal(apiV2->apiConfig))
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
"",
apiV2->serviceType(),
"",
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/account_info"), apiPayload, responseBody, isTestPurchase);
@@ -969,13 +1062,20 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
}
bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
.addField(apiDefs::key::serviceType, apiV2->serviceType())
.addField(apiDefs::key::authData, apiV2->authData.toJson())
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
.addField(apiDefs::key::subscriptionStatus, getSubscriptionStatusForRenewal(apiV2->apiConfig))
.build();
QJsonObject authDataJson = apiV2->authData.toJson();
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
apiV2->apiConfig.userCountryCode,
"",
apiV2->serviceType(),
"",
authDataJson };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::subscriptionStatus] = getSubscriptionStatusForRenewal(apiV2->apiConfig);
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
@@ -1011,14 +1111,21 @@ ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &use
const QString &captchaSolution,
CaptchaInfo *retryCaptchaOut)
{
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::userCountryCode, userCountryCode)
.addField(apiDefs::key::serviceType, serviceType)
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
.addField(apiDefs::key::captchaId, captchaId)
.addField(apiDefs::key::captchaSolution, normalizeCaptchaSolution(captchaSolution))
.build();
GatewayRequestData gatewayRequestData{QSysInfo::productType(),
QString(APP_VERSION),
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
m_appSettingsRepository->getInstallationUuid(true),
userCountryCode,
"",
serviceType,
serviceProtocol,
QJsonObject()};
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
apiPayload["captcha_id"] = captchaId;
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);

View File

@@ -27,6 +27,21 @@ public:
QString xrayUuid;
};
struct GatewayRequestData
{
QString osVersion;
QString appVersion;
QString appLanguage;
QString installationUuid;
QString userCountryCode;
QString serverCountryCode;
QString serviceType;
QString serviceProtocol;
QJsonObject authData;
QJsonObject toJsonObject() const;
};
struct CaptchaInfo {
QString captchaId;
QString captchaImageBase64;
@@ -38,8 +53,7 @@ public:
SecureAppSettingsRepository* appSettingsRepository);
ProtocolData generateProtocolData(const QString &protocol);
static QString publicKeyForProtocol(const QString &protocol, const ProtocolData &protocolData);
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const ProtocolData &protocolData,

View File

@@ -154,13 +154,8 @@ QString IpSplitTunnelingController::normalizeHostname(const QString &hostname) c
normalized.replace("https://", "");
normalized.replace("http://", "");
normalized.replace("ftp://", "");
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalized)) {
return normalized;
}
const QStringList parts = normalized.split("/", Qt::SkipEmptyParts);
return parts.isEmpty() ? QString() : parts.first();
normalized = normalized.split("/", Qt::SkipEmptyParts).first();
return normalized;
}
bool IpSplitTunnelingController::validateHostname(const QString &hostname) const

View File

@@ -5,13 +5,13 @@
#include <QUrl>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSysInfo>
#include <QTimer>
#include "amneziaApplication.h"
#include "logger.h"
#include "version.h"
#include "core/controllers/gatewayController.h"
#include "core/utils/api/gatewayPayloadBuilder.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
@@ -98,9 +98,10 @@ void UpdateController::fetchGatewayUrl()
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
.addField(apiDefs::key::cliVersion, QString(APP_VERSION))
.build();
QJsonObject apiPayload;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::installationUuid] = m_appSettingsRepository->getInstallationUuid(true);
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {

View File

@@ -1,6 +1,5 @@
#include "apiUtils.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/configKeys.h"
#include <QDateTime>
@@ -49,19 +48,6 @@ namespace
}
}
QString apiUtils::getAppLanguageCode(const SecureAppSettingsRepository *appSettingsRepository)
{
if (appSettingsRepository == nullptr) {
return {};
}
return appSettingsRepository->getAppLanguage().name().split("_").first();
}
QString apiUtils::getCountryFlagCode(const QString &serverCountryCode)
{
return serverCountryCode.section('-', 0, 0).toUpper();
}
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
if (subscriptionEndDate.isEmpty()) {

View File

@@ -11,17 +11,8 @@
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class SecureAppSettingsRepository;
namespace apiUtils
{
QString getAppLanguageCode(const SecureAppSettingsRepository *appSettingsRepository);
// The gateway may report a country code with a region suffix, e.g. "us-west", while flag
// resources are named after the ISO 3166-1 alpha-2 code alone. Returns the part before the dash
// in upper case.
QString getCountryFlagCode(const QString &serverCountryCode);
bool isSubscriptionExpired(const QString &subscriptionEndDate);
bool isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays = 30);

View File

@@ -1,56 +0,0 @@
#include "gatewayPayloadBuilder.h"
#include <QJsonArray>
#include <QSysInfo>
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "version.h"
namespace
{
bool isEmptyValue(const QJsonValue &value)
{
switch (value.type()) {
case QJsonValue::Null:
case QJsonValue::Undefined:
return true;
case QJsonValue::String:
return value.toString().isEmpty();
case QJsonValue::Object:
return value.toObject().isEmpty();
case QJsonValue::Array:
return value.toArray().isEmpty();
default:
return false;
}
}
}
GatewayPayloadBuilder::GatewayPayloadBuilder(const SecureAppSettingsRepository *appSettingsRepository)
{
m_payload[apiDefs::key::osVersion] = QSysInfo::productType();
m_payload[apiDefs::key::appVersion] = QString(APP_VERSION);
if (appSettingsRepository == nullptr) {
qWarning() << "SecureAppSettingsRepository is null, skip app language and installation uuid";
return;
}
m_payload[apiDefs::key::appLanguage] = apiUtils::getAppLanguageCode(appSettingsRepository);
m_payload[apiDefs::key::installationUuid] = appSettingsRepository->getInstallationUuid(true);
}
GatewayPayloadBuilder &GatewayPayloadBuilder::addField(QLatin1String key, const QJsonValue &value)
{
if (!isEmptyValue(value)) {
m_payload[key] = value;
}
return *this;
}
QJsonObject GatewayPayloadBuilder::build() const
{
return m_payload;
}

View File

@@ -1,22 +0,0 @@
#ifndef GATEWAYPAYLOADBUILDER_H
#define GATEWAYPAYLOADBUILDER_H
#include <QJsonObject>
#include <QJsonValue>
#include <QLatin1String>
class SecureAppSettingsRepository;
class GatewayPayloadBuilder
{
public:
explicit GatewayPayloadBuilder(const SecureAppSettingsRepository *appSettingsRepository);
GatewayPayloadBuilder &addField(QLatin1String key, const QJsonValue &value);
QJsonObject build() const;
private:
QJsonObject m_payload;
};
#endif // GATEWAYPAYLOADBUILDER_H

View File

@@ -40,10 +40,6 @@ namespace apiDefs
constexpr QLatin1String lastDownloaded("last_downloaded");
constexpr QLatin1String sourceType("source_type");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String locale("locale");
constexpr QLatin1String captchaId("captcha_id");
constexpr QLatin1String captchaSolution("captcha_solution");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");

View File

@@ -637,7 +637,7 @@ void Daemon::checkHandshake() {
pendingHandshakes++;
}
}
// Check again if there were connections that haven't completed a handshake.
if (pendingHandshakes > 0) {
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);

View File

@@ -7,10 +7,14 @@
#include <net/if.h>
#include <QDBusVariant>
#include <QNetworkInterface>
#include <QTimer>
#include <QtDBus/QtDBus>
#include "core/utils/networkUtilities.h"
#include "leakdetector.h"
#include "logger.h"
#include "router_linux.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
@@ -27,24 +31,56 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
auto* watcher = new QDBusServiceWatcher(
DBUS_RESOLVE_SERVICE, conn,
QDBusServiceWatcher::WatchForRegistration |
QDBusServiceWatcher::WatchForUnregistration, this);
connect(watcher, &QDBusServiceWatcher::serviceRegistered,
this, &DnsUtilsLinux::onResolverRegistered);
connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
this, &DnsUtilsLinux::onResolverUnregistered);
if (conn.interface()->isServiceRegistered(DBUS_RESOLVE_SERVICE)) {
onResolverRegistered();
}
}
void DnsUtilsLinux::onResolverRegistered() {
m_resolver.reset(new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER,
QDBusConnection::systemBus()));
logger.debug() << "systemd-resolved available, DNS resolver initialized";
if (!m_pendingIfname.isEmpty()) {
logger.debug() << "Re-applying DNS configuration for" << m_pendingIfname;
updateResolvers(m_pendingIfname, m_pendingResolvers);
}
}
void DnsUtilsLinux::onResolverUnregistered() {
logger.debug() << "systemd-resolved disappeared, dropping DNS resolver";
m_resolver.reset();
}
DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_resolver) {
if (m_gatewayIfindex > 0)
setLinkDefaultRoute(m_gatewayIfindex, true);
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
}
logger.debug() << "DnsUtilsLinux destroyed.";
@@ -52,19 +88,52 @@ DnsUtilsLinux::~DnsUtilsLinux() {
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) {
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
const int previousIfindex = m_ifindex;
m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
m_pendingIfname = ifname;
m_pendingResolvers = resolvers;
if (!m_resolver) {
logger.debug() << "systemd-resolved not ready, queuing DNS configuration";
return true;
}
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
if (previousIfindex > 0 && previousIfindex != m_ifindex) {
m_resolver->callWithArgumentList(QDBus::Block, QStringLiteral("RevertLink"),
{QVariant::fromValue(previousIfindex)});
}
return true;
}
bool DnsUtilsLinux::restoreResolvers() {
m_pendingIfname.clear();
m_pendingResolvers.clear();
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value());
@@ -72,7 +141,7 @@ bool DnsUtilsLinux::restoreResolvers() {
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) {
if (m_ifindex > 0 && m_resolver) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
@@ -90,13 +159,17 @@ bool DnsUtilsLinux::restoreResolvers() {
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)";
logger.debug() << "Restarting resolved to clear its query backlog";
RouterLinux::Instance().flushDns();
scheduleRetry();
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
if (!m_resolver) return;
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
@@ -121,6 +194,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
if (!m_resolver) return;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
@@ -144,6 +218,7 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
if (!m_resolver) return;
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
@@ -156,6 +231,7 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
}
void DnsUtilsLinux::updateLinkDomains() {
if (!m_resolver) return;
/* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types.
@@ -174,11 +250,20 @@ void DnsUtilsLinux::updateLinkDomains() {
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
call->deleteLater();
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
// systemd-resolved may still be starting up after a restart — retry a few times
if (m_ifindex > 0 && m_domainRetries++ < 5) {
logger.debug() << "systemd-resolved not ready yet, retrying DNS setup ("
<< m_domainRetries << "/5)";
QTimer::singleShot(500, this, &DnsUtilsLinux::updateLinkDomains);
} else {
logger.warning() << "Failed to configure DNS after 5 retries";
m_domainRetries = 0;
}
return;
}
m_domainRetries = 0;
/* Update the state of the DNS domains */
m_linkDomains.clear();
@@ -204,9 +289,30 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
if (m_ifindex > 0) {
setLinkDomains(m_ifindex, {root});
/* Disable DefaultRoute on the physical gateway so systemd-resolved
* routes all DNS through the VPN interface. */
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
}
}
void DnsUtilsLinux::scheduleRetry() {
if (m_pendingIfname.isEmpty() || m_retryPending || m_domainRetries >= 5)
return;
m_retryPending = true;
++m_domainRetries;
logger.debug() << "Retrying full DNS setup (" << m_domainRetries << "/5)";
QTimer::singleShot(1000, this, [this]() {
m_retryPending = false;
if (!m_pendingIfname.isEmpty())
updateResolvers(m_pendingIfname, m_pendingResolvers);
});
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;

View File

@@ -6,7 +6,12 @@
#define DNSUTILSLINUX_H
#include <QDBusInterface>
#include <QScopedPointer>
#include <QDBusPendingCallWatcher>
#include <QDBusServiceWatcher>
#include <QHostAddress>
#include <QList>
#include <QString>
#include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
@@ -29,13 +34,22 @@ class DnsUtilsLinux final : public DnsUtils {
void updateLinkDomains();
private slots:
void onResolverRegistered();
void onResolverUnregistered();
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
private:
void scheduleRetry();
int m_ifindex = 0;
int m_gatewayIfindex = 0;
int m_domainRetries = 0;
bool m_retryPending = false;
QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr;
QScopedPointer<QDBusInterface> m_resolver;
QString m_pendingIfname;
QList<QHostAddress> m_pendingResolvers;
};
#endif // DNSUTILSLINUX_H

View File

@@ -33,6 +33,7 @@
#include "linuxfirewall.h"
#include "logger.h"
#include "xray_defs.h"
#include <QFileInfo>
#include <QProcess>
#define BRAND_CODE "amn"
@@ -109,7 +110,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs -r %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
@@ -501,13 +502,22 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
if (!err.isEmpty() && !ignoreErrors)
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
const QString cgroupBase = QStringLiteral("/sys/fs/cgroup/net_cls");
if (!QFileInfo::exists(cgroupBase)) {
logger.warning() << "net_cls cgroup v1 not available, traffic splitting disabled";
return;
}
execute(QStringLiteral(
"if ! grep -qE '^[0-9]+[[:space:]]+%1$' /etc/iproute2/rt_tables 2>/dev/null ; then "
"echo '200 %1' >> /etc/iproute2/rt_tables ; fi"
).arg(kRtableName));
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
@@ -519,6 +529,7 @@ void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName), true);
execute(QStringLiteral("ip route flush cache"));
execute(QStringLiteral("sed -i '/%1/d' /etc/iproute2/rt_tables").arg(kRtableName));
}

View File

@@ -263,7 +263,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
if (!config.m_serverIpv4AddrIn.isEmpty() &&
!m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn))) {
logger.error() << "No gateway — cannot add server exclusion route";
return false;
}
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}

File diff suppressed because it is too large Load Diff

View File

@@ -272,7 +272,7 @@ bool SubscriptionUiController::restoreServiceFromAppStore()
return false;
}
emit installServerFromApiFinished(tr("Subscription restored successfully"));
emit installServerFromApiFinished(tr("Subscription restored successfully."));
if (result.duplicateCount > 0) {
qInfo().noquote() << "[IAP] Skipped" << result.duplicateCount
<< "duplicate restored transactions for original transaction IDs already processed";
@@ -469,7 +469,7 @@ bool SubscriptionUiController::importTrialFromGateway(const QString &email)
if (errorCode != ErrorCode::NoError) {
if (errorCode == ErrorCode::ApiTrialAlreadyUsedError) {
emit trialEmailError(
tr("This email address has already been used to activate a trial. Like the service? Upgrade to Premium"));
tr("This email address has already been used to activate a trial. If you like the service, you can upgrade to Premium"));
} else {
emit errorOccurred(errorCode);
}
@@ -648,7 +648,7 @@ QStringList SubscriptionUiController::availableProtocols(const QString &serverId
void SubscriptionUiController::removeApiConfig(const QString &serverId)
{
m_subscriptionController->removeApiConfig(serverId);
emit apiConfigRemoved(tr("API config removed"));
emit apiConfigRemoved(tr("Api config removed"));
}
void SubscriptionUiController::removeServer(const QString &serverId)

View File

@@ -1,6 +1,5 @@
#include "serversUiController.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
@@ -206,14 +205,10 @@ QString ServersUiController::getDefaultServerDescriptionCollapsed() const
QString ServersUiController::getDefaultServerImagePathCollapsed() const
{
const auto &description = serverDescriptionById(getDefaultServerId());
if (!description.isApiV2) {
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
return "";
}
const QString flagCode = apiUtils::getCountryFlagCode(description.apiServerCountryCode);
if (flagCode.isEmpty()) {
return "";
}
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(flagCode);
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
}
QString ServersUiController::getDefaultServerDescriptionExpanded() const

View File

@@ -2,7 +2,6 @@
#include <QJsonObject>
#include "core/utils/api/apiUtils.h"
#include "core/utils/serverConfigUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
@@ -42,7 +41,7 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
return countryInfo.countryName;
}
case CountryImageCodeRole: {
return apiUtils::getCountryFlagCode(countryInfo.countryCode);
return countryInfo.countryCode.toUpper();
}
case IsIssuedRole: {
return isIssued;

View File

@@ -63,8 +63,8 @@ Menu {
}
MenuItem {
text: qsTr("&Paste")
// Fix calling paste from clipboard when launching app on android/ios
enabled: (Qt.platform.os === "android" || Qt.platform.os === "ios") ? true : textObj.canPaste
// Fix calling paste from clipboard when launching app on android
enabled: Qt.platform.os === "android" ? true : textObj.canPaste
onTriggered: textObj.paste()
}

View File

@@ -25,8 +25,6 @@ PageType {
property string apiCurrentProtocol: ""
readonly property bool isApiProtocolSelectionVisible: ServersUiController.isDefaultServerFromApi && root.apiAvailableProtocols.length > 0
readonly property bool isOutdatedAwgWarningVisible: drawer.isCollapsedStateActive()
&& ServersUiController.defaultServerHasOutdatedAwgContainer
function updateApiProtocolState() {
if (ServersUiController.isDefaultServerFromApi) {
@@ -301,6 +299,26 @@ PageType {
}
}
ImageButtonType {
id: outdatedContainerWarningIcon
objectName: "outdatedContainerWarningIcon"
Layout.rightMargin: 8
visible: drawer.isCollapsedStateActive() && ServersUiController.defaultServerHasOutdatedAwgContainer
hoverEnabled: false
image: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
icon.width: 18
icon.height: 18
backgroundRadius: 16
horizontalPadding: 4
topPadding: 4
bottomPadding: 3
}
Header1TextType {
id: collapsedButtonHeader
objectName: "collapsedButtonHeader"
@@ -352,9 +370,7 @@ PageType {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: root.isOutdatedAwgWarningVisible
? 8
: (root.isApiProtocolSelectionVisible ? 8 : (drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16))
Layout.bottomMargin: root.isApiProtocolSelectionVisible ? 8 : (drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16)
spacing: 0
BasicButtonType {
@@ -409,23 +425,6 @@ PageType {
}
}
WarningType {
objectName: "outdatedContainerWarning"
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 24
visible: root.isOutdatedAwgWarningVisible
backGroundColor: AmneziaStyle.color.transparent
iconPath: "qrc:/images/controls/alert-circle.svg"
imageColor: AmneziaStyle.color.goldenApricot
textColor: AmneziaStyle.color.goldenApricot
textString: qsTr("AmneziaWG 2.0 is outdated and no longer supported. Continued use requires a fresh installation of the AmneziaWG 3.0 container.")
}
RowLayout {
objectName: "protocolRowLayout"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter

View File

@@ -235,7 +235,7 @@ PageType {
Layout.rightMargin: 32
Layout.alignment: Qt.AlignRight
source: countryImageCode !== "" ? "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" : ""
source: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
}
}

View File

@@ -74,7 +74,7 @@ PageType {
hideDescription: isWorkerExpired ? false : true
descriptionColor: AmneziaStyle.color.vibrantRed
leftImageSource: countryImageCode !== "" ? "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg" : ""
leftImageSource: "qrc:/countriesFlags/images/flagKit/" + countryImageCode + ".svg"
rightImageSource: isIssued ? "qrc:/images/controls/more-vertical.svg" : "qrc:/images/controls/download.svg"
clickedFunction: function() {

View File

@@ -72,17 +72,6 @@ if(WIN32)
DESTINATION "."
COMPONENT AmneziaVPN
)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
if(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
install(PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION "."
COMPONENT AmneziaVPN
)
else()
message(WARNING "MSVC runtime libraries were not found, packages will not ship them")
endif()
endif()
if (APPLE AND NOT IOS AND NOT MACOS_NE)

View File

@@ -123,7 +123,7 @@ case "$TARGET" in
android)
no_installers=1
: ${CMAKE_GENERATOR:="Ninja"}
: ${ANDROID_PLATFORM:="android-${APP_ANDROID_MIN_SDK:-28}"}
: ${ANDROID_PLATFORM:="android-28"}
if [[ -n "$SIGN" ]]; then
QT_ANDROID_SIGN_APK=TRUE
@@ -198,9 +198,6 @@ args=()
[[ -n "$ANDROID_SDK_ROOT" ]] && args+=("-DANDROID_SDK_ROOT=$ANDROID_SDK_ROOT")
[[ -n "$ANDROID_NDK_ROOT" ]] && args+=("-DANDROID_NDK_ROOT=$ANDROID_NDK_ROOT")
[[ -n "$ANDROID_PLATFORM" ]] && args+=("-DANDROID_PLATFORM=$ANDROID_PLATFORM")
[[ -n "$APP_ANDROID_MIN_SDK" ]] && args+=("-DAPP_ANDROID_MIN_SDK=$APP_ANDROID_MIN_SDK")
[[ -n "$APP_ANDROID_MAX_SDK" ]] && args+=("-DAPP_ANDROID_MAX_SDK=$APP_ANDROID_MAX_SDK")
[[ -n "$APP_ANDROID_VERSION_CODE_OFFSET" ]] && args+=("-DAPP_ANDROID_VERSION_CODE_OFFSET=$APP_ANDROID_VERSION_CODE_OFFSET")
[[ -n "$QT_ANDROID_SIGN_APK" ]] && args+=("-DQT_ANDROID_SIGN_APK=$QT_ANDROID_SIGN_APK")
[[ -n "$QT_ANDROID_SIGN_AAB" ]] && args+=("-DQT_ANDROID_SIGN_AAB=$QT_ANDROID_SIGN_AAB")
[[ -n "$QT_ANDROID_ABIS" ]] && args+=("-DQT_ANDROID_ABIS=$QT_ANDROID_ABIS")

View File

@@ -34,6 +34,10 @@ download_file() {
echo "Successfully downloaded $filename"
}
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86_64.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.run
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_x64.pkg
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe

View File

@@ -73,15 +73,15 @@ Component.prototype.createOperations = function()
"workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\" + appExecutableFileName(), "iconId=0");
if (!vcRuntimeIsInstalled()) {
var vcRedistFileName = (systemInfo.currentCpuArchitecture.search("64") < 0) ? "vc_redist.x86.exe"
: "vc_redist.x64.exe";
if (installer.findPath(vcRedistFileName, [installer.value("TargetDir").replace(/\//g, '\\')]).length !== 0) {
component.addElevatedOperation("Execute", "@TargetDir@\\" + vcRedistFileName, "/install", "/quiet", "/norestart", "/log", "vc_redist.log");
} else {
console.log(vcRedistFileName + " is not bundled, relying on the application-local MSVC runtime");
}
if (systemInfo.currentCpuArchitecture.search("64") < 0) {
component.addElevatedOperation("Execute", "@TargetDir@\\" + "vc_redist.x86.exe", "/install", "/quiet", "/norestart", "/log", "vc_redist.log");
}
else {
component.addElevatedOperation("Execute", "@TargetDir@\\" + "vc_redist.x64.exe", "/install", "/quiet", "/norestart", "/log", "vc_redist.log");
}
} else {
console.log("Microsoft Visual C++ Redistributable already installed");
console.log("Microsoft Visual C++ 2017 Redistributable already installed");
}
let pu_path = installer.value("TargetDir").replace(/\//g, '\\') + "\\"

View File

@@ -178,11 +178,16 @@ bool RouterLinux::flushDns()
}
p.waitForFinished();
QByteArray output(p.readAll());
QByteArray output = p.readAll();
if ((p.exitStatus() != QProcess::NormalExit) || (p.exitCode() != 0)) {
qDebug().noquote() << "Failed to flush DNS: " + output;
return false;
}
if (output.isEmpty())
qDebug().noquote() << "Flush dns completed";
else
qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output;
qDebug().noquote() << "OUTPUT systemctl restart: " + output;
return true;
}