mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 02:01:03 +07:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd382546ac | |||
| 8c5b4781d5 | |||
| 8da439f0b3 | |||
| e9cd043b10 | |||
| eda9ed8016 | |||
| 225b693ea4 | |||
| 97b2de8cd1 | |||
| ef1ed7064a | |||
| 40ba31f54b | |||
| 7bb609866a | |||
| 54d28862f3 | |||
| bff3e228fc | |||
| d528a241d8 | |||
| 99e6c18f15 | |||
| 6d49a9416e | |||
| 80fa788802 | |||
| 3590b2d323 | |||
| 9a1e380ffb | |||
| eb42ce8fef | |||
| 72147d3a67 | |||
| 39f9bcfd50 | |||
| b6188baeb8 | |||
| 9b329ad5b1 | |||
| ce05b4e99c | |||
| 75f522e9dc | |||
| d6349b5734 | |||
| fa8014093b | |||
| 9d69ab89d5 | |||
| f2ff8a7b3b | |||
| 864b8c6f8a | |||
| adb8eb4937 | |||
| 6750afd330 | |||
| 7ad0692306 | |||
| 0dcd05c6c3 | |||
| 83e82c16a7 | |||
| f29b6cf027 | |||
| 4bca2df4a2 | |||
| f67927667a | |||
| cc469e74ed | |||
| 234c70f495 | |||
| 850b698e83 | |||
| 42570c54f8 | |||
| 890103a16a | |||
| 56ab82f87f | |||
| 3984acbb44 | |||
| cc404378f9 | |||
| 594635e5cf | |||
| f9b106cf5b | |||
| a9861d18b7 | |||
| c14138f031 | |||
| 60686fde24 | |||
| bd0747296e | |||
| ba61019a50 | |||
| 113f967006 | |||
| bcee58b08b | |||
| 52de1acebf | |||
| 027a12a1df | |||
| 0a659a2d74 | |||
| 6f119cd083 | |||
| 1753aed3fc | |||
| c714d98bd1 | |||
| 4787f3915b | |||
| 7a383116b2 | |||
| d3de5f0f48 | |||
| 8749d683e3 | |||
| 9de9d082bc | |||
| a4233fef41 | |||
| 4890dd1d74 | |||
| 564630827e |
@@ -18,11 +18,14 @@ jobs:
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
base: ${{ github.event.before }}
|
||||
filters: |
|
||||
recipes:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -40,7 +43,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
shell: bash
|
||||
@@ -100,7 +103,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Install system packages'
|
||||
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
@@ -151,10 +154,10 @@ jobs:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
@@ -233,7 +236,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
@@ -260,13 +263,17 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-iOS:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [26.0, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -283,7 +290,7 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
|
||||
@@ -350,7 +357,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.4'
|
||||
xcode-version: '26.0'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -382,7 +389,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install deps'
|
||||
run: pip install "conan==2.26.2" jsonschema jinja2
|
||||
run: pip install "conan==2.28.0" jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
@@ -400,14 +407,17 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -424,7 +434,7 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
|
||||
@@ -510,7 +520,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
@@ -533,13 +543,17 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4]
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -556,7 +570,7 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
|
||||
@@ -645,7 +659,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
@@ -681,7 +695,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
@@ -818,7 +832,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.26.2"
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
@@ -906,3 +920,4 @@ jobs:
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.9.0)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -18,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
# trigger conan to kick off `conan install`
|
||||
find_package(OpenSSL REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -28,7 +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})
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
+23
-6
@@ -193,10 +193,6 @@ elseif(APPLE)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
@@ -216,11 +212,32 @@ endif()
|
||||
|
||||
install(TARGETS ${PROJECT}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
RUNTIME_DEPENDENCY_SET client_deps
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
|
||||
if(APPLE)
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
|
||||
else()
|
||||
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
PRE_EXCLUDE_REGEXES
|
||||
[[api-ms-win-.*]]
|
||||
[[ext-ms-.*]]
|
||||
[[kernel32\.dll]]
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
[[^/usr/lib.*]]
|
||||
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
|
||||
COMPONENT AmneziaVPN
|
||||
DESTINATION "${RUNTIME_DEPS_DIR}"
|
||||
)
|
||||
|
||||
set(deploy_tool_options "")
|
||||
|
||||
@@ -54,7 +54,6 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
|
||||
@@ -65,6 +65,8 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/constants.h
|
||||
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h
|
||||
${CLIENT_ROOT_DIR}/core/tunnel.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -145,6 +147,8 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
|
||||
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp
|
||||
${CLIENT_ROOT_DIR}/core/tunnel.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QThread>
|
||||
#include <QUuid>
|
||||
#include "logger.h"
|
||||
|
||||
@@ -137,117 +138,326 @@ amnezia::ProtocolConfig XrayConfigurator::processConfigWithLocalSettings(const a
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode XrayConfigurator::uploadServerConfigJson(const ServerCredentials &credentials, DockerContainer container,
|
||||
const DnsSettings &dnsSettings, const QJsonObject &serverConfig) const
|
||||
{
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
const QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
ErrorCode errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container, credentials, updatedConfig, amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
// Get flow value from settings (default xtls-rprx-vision)
|
||||
QString flowValue = "xtls-rprx-vision";
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
if (!xrayCfg->serverConfig.flow.isEmpty()) {
|
||||
flowValue = xrayCfg->serverConfig.flow;
|
||||
const QString restartScript = QStringLiteral("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(restartScript,
|
||||
amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns,
|
||||
dnsSettings.secondaryDns)));
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::readRealityKeyFiles(const DockerContainer container, const ServerCredentials &credentials,
|
||||
QString &outPublicKey, QString &outShortId) const
|
||||
{
|
||||
outPublicKey.clear();
|
||||
outShortId.clear();
|
||||
|
||||
auto readKeyFile = [&](const QString &path, QString &out) -> ErrorCode {
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
ErrorCode fileError = ErrorCode::NoError;
|
||||
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
|
||||
out.replace(QLatin1Char('\n'), QString());
|
||||
out.replace(QLatin1Char('\r'), QString());
|
||||
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
if (attempt < 2) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
logger.error() << "Xray readRealityKeyFiles: failed path=" << path;
|
||||
return ErrorCode::XrayRealityKeysReadFailed;
|
||||
};
|
||||
|
||||
ErrorCode errorCode = readKeyFile(QString::fromLatin1(amnezia::protocols::xray::PublicKeyPath), outPublicKey);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
return readKeyFile(QString::fromLatin1(amnezia::protocols::xray::shortidPath), outShortId);
|
||||
}
|
||||
|
||||
QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServerConfig &srv,
|
||||
const QJsonObject &existingStreamSettings) const
|
||||
{
|
||||
QJsonObject streamSettings = buildStreamSettings(srv, QString());
|
||||
|
||||
if (srv.security != QLatin1String("reality")) {
|
||||
return streamSettings;
|
||||
}
|
||||
|
||||
const QJsonObject newRs = streamSettings[amnezia::protocols::xray::realitySettings].toObject();
|
||||
QJsonObject oldRs = existingStreamSettings[amnezia::protocols::xray::realitySettings].toObject();
|
||||
QJsonObject merged = oldRs.isEmpty() ? newRs : oldRs;
|
||||
|
||||
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(amnezia::protocols::xray::defaultSite) : srv.site;
|
||||
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
|
||||
|
||||
if (newRs.contains(amnezia::protocols::xray::fingerprint)) {
|
||||
merged[amnezia::protocols::xray::fingerprint] = newRs[amnezia::protocols::xray::fingerprint];
|
||||
}
|
||||
merged[amnezia::protocols::xray::serverNames] = QJsonArray { sniEff };
|
||||
if (!merged.contains(QStringLiteral("dest"))) {
|
||||
merged[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
|
||||
}
|
||||
|
||||
streamSettings[amnezia::protocols::xray::realitySettings] = merged;
|
||||
return streamSettings;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials &credentials, DockerContainer container,
|
||||
ContainerConfig &containerConfig, const DnsSettings &dnsSettings,
|
||||
bool appendNewClient, QString *outClientId)
|
||||
{
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
|
||||
if (!xrayCfg) {
|
||||
logger.error() << "Xray applyServerSettings: missing XrayProtocolConfig";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
const XrayServerConfig &srv = xrayCfg->serverConfig;
|
||||
if (srv.isThirdPartyConfig) {
|
||||
logger.info() << "Xray applyServerSettings: skipped (third-party/native profile)";
|
||||
if (outClientId && xrayCfg->hasClientConfig()) {
|
||||
*outClientId = xrayCfg->clientConfig->id;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
logger.info() << "Xray applyServerSettings: start"
|
||||
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
|
||||
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
|
||||
<< "appendClient=" << appendNewClient;
|
||||
QString flowValue = srv.flow;
|
||||
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
|
||||
flowValue = QStringLiteral("xtls-rprx-vision");
|
||||
}
|
||||
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_sshSession->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get server config file";
|
||||
return "";
|
||||
logger.error() << "Xray applyServerSettings: getTextFileFromContainer failed, error="
|
||||
<< static_cast<int>(errorCode) << "path=" << amnezia::protocols::xray::serverConfigPath;
|
||||
return errorCode;
|
||||
}
|
||||
logger.info() << "Xray applyServerSettings: read server config, bytes=" << currentConfig.size();
|
||||
|
||||
// Parse current config as JSON
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains(amnezia::protocols::xray::settings)) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
return ErrorCode::XrayServerConfigInvalid;
|
||||
}
|
||||
|
||||
const QJsonObject existingStream = inbound[amnezia::protocols::xray::streamSettings].toObject();
|
||||
inbound[amnezia::protocols::xray::streamSettings] = mergeStreamSettingsForServerInbound(srv, existingStream);
|
||||
|
||||
if (!srv.port.isEmpty()) {
|
||||
inbound[amnezia::protocols::xray::port] = srv.port.toInt();
|
||||
}
|
||||
|
||||
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
|
||||
if (!settings.contains(amnezia::protocols::xray::clients)) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
settings[amnezia::protocols::xray::clients] = QJsonArray {};
|
||||
}
|
||||
|
||||
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
|
||||
QString clientId;
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{amnezia::protocols::xray::id, clientId},
|
||||
};
|
||||
clientConfig[amnezia::protocols::xray::id] = clientId;
|
||||
if (!flowValue.isEmpty()) {
|
||||
clientConfig[amnezia::protocols::xray::flow] = flowValue;
|
||||
if (appendNewClient) {
|
||||
clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
QJsonObject clientEntry;
|
||||
clientEntry[amnezia::protocols::xray::id] = clientId;
|
||||
if (!flowValue.isEmpty()) {
|
||||
clientEntry[amnezia::protocols::xray::flow] = flowValue;
|
||||
}
|
||||
clients.append(clientEntry);
|
||||
} else {
|
||||
if (clients.isEmpty()) {
|
||||
logger.error() << "Server config has no VLESS clients";
|
||||
return ErrorCode::XrayServerNoVlessClients;
|
||||
}
|
||||
clientId = clients[0].toObject()[amnezia::protocols::xray::id].toString();
|
||||
if (clientId.isEmpty()) {
|
||||
logger.error() << "Server config VLESS client has empty id";
|
||||
return ErrorCode::XrayServerNoVlessClients;
|
||||
}
|
||||
QJsonArray updatedClients;
|
||||
for (const QJsonValue &v : clients) {
|
||||
QJsonObject c = v.toObject();
|
||||
if (flowValue.isEmpty()) {
|
||||
c.remove(amnezia::protocols::xray::flow);
|
||||
} else {
|
||||
c[amnezia::protocols::xray::flow] = flowValue;
|
||||
}
|
||||
updatedClients.append(c);
|
||||
}
|
||||
clients = updatedClients;
|
||||
}
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings[amnezia::protocols::xray::clients] = clients;
|
||||
inbound[amnezia::protocols::xray::settings] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
);
|
||||
errorCode = uploadServerConfigJson(credentials, container, dnsSettings, serverConfig);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return "";
|
||||
logger.error() << "Xray applyServerSettings: upload/restart failed, error=" << static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
logger.info() << "Xray applyServerSettings: server config uploaded and container restarted";
|
||||
|
||||
if (outClientId) {
|
||||
*outClientId = clientId;
|
||||
}
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_sshSession->runScript(
|
||||
credentials,
|
||||
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
|
||||
);
|
||||
|
||||
XrayProtocolConfig updated =
|
||||
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
return "";
|
||||
logger.error() << "Xray applyServerSettings: buildClientProtocolConfig failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
containerConfig.protocolConfig = updated;
|
||||
logger.info() << "Xray applyServerSettings: done, clientId=" << clientId;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
ContainerConfig mutableConfig = containerConfig;
|
||||
QString clientId;
|
||||
const ErrorCode applyError =
|
||||
applyServerSettingsToRemote(credentials, container, mutableConfig, dnsSettings, true, &clientId);
|
||||
errorCode = applyError;
|
||||
if (applyError != ErrorCode::NoError || clientId.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const XrayServerConfig &srv, const QString &clientId,
|
||||
ErrorCode &errorCode,
|
||||
const QString &prefetchedRealityPublicKey,
|
||||
const QString &prefetchedRealityShortId) const
|
||||
{
|
||||
QString xrayPublicKey = prefetchedRealityPublicKey;
|
||||
QString xrayShortId = prefetchedRealityShortId;
|
||||
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject userObj;
|
||||
userObj[amnezia::protocols::xray::id] = clientId;
|
||||
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
|
||||
if (!srv.flow.isEmpty()) {
|
||||
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
||||
}
|
||||
|
||||
QJsonObject vnextEntry;
|
||||
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
|
||||
vnextEntry[amnezia::protocols::xray::port] =
|
||||
srv.port.isEmpty() ? QString(amnezia::protocols::xray::defaultPort).toInt() : srv.port.toInt();
|
||||
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
|
||||
|
||||
QJsonObject outboundSettings;
|
||||
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound[QStringLiteral("protocol")] = QStringLiteral("vless");
|
||||
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
||||
|
||||
QJsonObject streamObj = buildStreamSettings(srv, clientId);
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
||||
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
||||
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
||||
rs[amnezia::protocols::xray::spiderX] = QString();
|
||||
streamObj[amnezia::protocols::xray::realitySettings] = rs;
|
||||
}
|
||||
|
||||
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
|
||||
|
||||
QJsonObject inboundObj;
|
||||
inboundObj[QStringLiteral("listen")] = amnezia::protocols::xray::defaultLocalListenAddr;
|
||||
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
||||
inboundObj[QStringLiteral("protocol")] = QStringLiteral("socks");
|
||||
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { QStringLiteral("udp"), true } };
|
||||
|
||||
QJsonObject clientJson;
|
||||
clientJson[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
|
||||
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
|
||||
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
|
||||
|
||||
const QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||
|
||||
XrayProtocolConfig protocolConfig;
|
||||
protocolConfig.serverConfig = srv;
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
|
||||
clientConfig.id = clientId;
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, const QString &clientId) const
|
||||
{
|
||||
QJsonObject streamSettings;
|
||||
@@ -419,6 +629,13 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
|
||||
const DnsSettings &dnsSettings,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
if (xrayCfg->serverConfig.isThirdPartyConfig && xrayCfg->hasClientConfig()) {
|
||||
logger.info() << "Xray createConfig: returning existing third-party client config without server SSH";
|
||||
return *xrayCfg;
|
||||
}
|
||||
}
|
||||
|
||||
const XrayServerConfig *serverConfig = nullptr;
|
||||
if (const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
|
||||
serverConfig = &xrayCfg->serverConfig;
|
||||
@@ -441,93 +658,5 @@ ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentia
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
|
||||
// Fetch server keys (Reality only)
|
||||
QString xrayPublicKey;
|
||||
QString xrayShortId;
|
||||
|
||||
if (srv.security == "reality") {
|
||||
xrayPublicKey = m_sshSession->getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||
logger.error() << "Failed to get public key";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
xrayShortId = m_sshSession->getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::xray::shortidPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||
logger.error() << "Failed to get short ID";
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::InternalError;
|
||||
}
|
||||
return XrayProtocolConfig{};
|
||||
}
|
||||
xrayShortId.replace("\n", "");
|
||||
}
|
||||
|
||||
// Build outbound
|
||||
QJsonObject userObj;
|
||||
userObj[amnezia::protocols::xray::id] = xrayClientId;
|
||||
userObj[amnezia::protocols::xray::encryption] = "none";
|
||||
if (!srv.flow.isEmpty()) {
|
||||
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
||||
}
|
||||
|
||||
QJsonObject vnextEntry;
|
||||
vnextEntry[amnezia::protocols::xray::address] = credentials.hostName;
|
||||
vnextEntry[amnezia::protocols::xray::port] = srv.port.toInt();
|
||||
vnextEntry[amnezia::protocols::xray::users] = QJsonArray { userObj };
|
||||
|
||||
QJsonObject outboundSettings;
|
||||
outboundSettings[amnezia::protocols::xray::vnext] = QJsonArray { vnextEntry };
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound["protocol"] = "vless";
|
||||
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
||||
|
||||
// Build streamSettings
|
||||
QJsonObject streamObj = buildStreamSettings(srv, xrayClientId);
|
||||
|
||||
// Inject Reality keys
|
||||
if (srv.security == "reality") {
|
||||
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
||||
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
||||
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
||||
rs[amnezia::protocols::xray::spiderX] = "";
|
||||
streamObj[amnezia::protocols::xray::realitySettings] = rs;
|
||||
}
|
||||
|
||||
outbound[amnezia::protocols::xray::streamSettings] = streamObj;
|
||||
|
||||
// Build full client config
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["listen"] = amnezia::protocols::xray::defaultLocalListenAddr;
|
||||
inboundObj[amnezia::protocols::xray::port] = amnezia::protocols::xray::defaultLocalProxyPort;
|
||||
inboundObj["protocol"] = "socks";
|
||||
inboundObj[amnezia::protocols::xray::settings] = QJsonObject { { "udp", true } };
|
||||
|
||||
QJsonObject clientJson;
|
||||
clientJson["log"] = QJsonObject { { "loglevel", "error" } };
|
||||
clientJson[amnezia::protocols::xray::inbounds] = QJsonArray { inboundObj };
|
||||
clientJson[amnezia::protocols::xray::outbounds] = QJsonArray { outbound };
|
||||
|
||||
QString config = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||
|
||||
// Return
|
||||
XrayProtocolConfig protocolConfig;
|
||||
protocolConfig.serverConfig = srv;
|
||||
|
||||
XrayClientConfig clientConfig;
|
||||
clientConfig.nativeConfig = config;
|
||||
qDebug() << "config:" << config;
|
||||
clientConfig.localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort);
|
||||
clientConfig.id = xrayClientId;
|
||||
|
||||
protocolConfig.setClientConfig(clientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
return buildClientProtocolConfig(credentials, container, srv, xrayClientId, errorCode);
|
||||
}
|
||||
@@ -23,12 +23,37 @@ public:
|
||||
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
|
||||
amnezia::ProtocolConfig protocolConfig) override;
|
||||
|
||||
amnezia::ErrorCode applyServerSettingsToRemote(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
bool appendNewClient,
|
||||
QString *outClientId = nullptr);
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
|
||||
// Builds the native xray "streamSettings" JSON object from XrayServerConfig
|
||||
amnezia::ErrorCode uploadServerConfigJson(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
|
||||
const amnezia::DnsSettings &dnsSettings, const QJsonObject &serverConfig) const;
|
||||
|
||||
amnezia::XrayProtocolConfig buildClientProtocolConfig(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
const amnezia::XrayServerConfig &srv,
|
||||
const QString &clientId,
|
||||
amnezia::ErrorCode &errorCode,
|
||||
const QString &prefetchedRealityPublicKey = {},
|
||||
const QString &prefetchedRealityShortId = {}) const;
|
||||
|
||||
amnezia::ErrorCode readRealityKeyFiles(amnezia::DockerContainer container,
|
||||
const amnezia::ServerCredentials &credentials,
|
||||
QString &outPublicKey,
|
||||
QString &outShortId) const;
|
||||
|
||||
QJsonObject mergeStreamSettingsForServerInbound(const amnezia::XrayServerConfig &srv,
|
||||
const QJsonObject &existingStreamSettings) const;
|
||||
|
||||
QJsonObject buildStreamSettings(const amnezia::XrayServerConfig &srv,
|
||||
const QString &clientId) const;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QSysInfo>
|
||||
@@ -216,7 +217,8 @@ ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData)
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo)
|
||||
{
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
@@ -233,6 +235,19 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
captchaInfo.captchaId = jsonObj.value("captcha_id").toString();
|
||||
captchaInfo.captchaImageBase64 = jsonObj.value("captcha_image").toString();
|
||||
captchaInfo.hint = jsonObj.value("hint").toString();
|
||||
captchaInfo.isRequired = true;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -242,9 +257,9 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
@@ -460,6 +475,7 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
|
||||
if (apiV2->nameOverriddenByUser) {
|
||||
newApiV2->name = apiV2->name;
|
||||
newApiV2->displayName = apiV2->displayName;
|
||||
newApiV2->nameOverriddenByUser = true;
|
||||
}
|
||||
|
||||
@@ -468,6 +484,12 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void SubscriptionController::restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config)
|
||||
{
|
||||
const QJsonObject json = config.toJson();
|
||||
m_serversRepository->editServer(serverId, json, serverConfigUtils::configTypeFromJson(json));
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
@@ -955,3 +977,74 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &userCountryCode,
|
||||
const QString &serviceType,
|
||||
const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData,
|
||||
const QString &captchaId,
|
||||
const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut)
|
||||
{
|
||||
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;
|
||||
QString normalizedSolution;
|
||||
normalizedSolution.reserve(captchaSolution.size());
|
||||
for (const QChar &ch : captchaSolution) {
|
||||
const ushort u = ch.unicode();
|
||||
if (u >= '0' && u <= '9') {
|
||||
normalizedSolution += ch;
|
||||
} else if (u >= 0xFF10 && u <= 0xFF19) {
|
||||
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
|
||||
}
|
||||
}
|
||||
apiPayload["captcha_solution"] = normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
const QJsonObject jsonObj = jsonDoc.object();
|
||||
if (jsonObj.contains(QStringLiteral("captcha_id")) && jsonObj.contains(QStringLiteral("captcha_image"))) {
|
||||
retryCaptchaOut->captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
|
||||
retryCaptchaOut->captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
|
||||
retryCaptchaOut->hint = jsonObj.value(QStringLiteral("hint")).toString();
|
||||
retryCaptchaOut->isRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, serviceType, serviceProtocol, userCountryCode, responseBody);
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig apiV2ServerConfig = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
m_serversRepository->addServer(QString(), apiV2ServerConfig.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(apiV2ServerConfig.toJson()));
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ public:
|
||||
QJsonObject toJsonObject() const;
|
||||
};
|
||||
|
||||
struct CaptchaInfo {
|
||||
QString captchaId;
|
||||
QString captchaImageBase64;
|
||||
QString hint;
|
||||
bool isRequired = false;
|
||||
};
|
||||
|
||||
explicit SubscriptionController(SecureServersRepository* serversRepository,
|
||||
SecureAppSettingsRepository* appSettingsRepository);
|
||||
|
||||
@@ -49,7 +56,8 @@ public:
|
||||
void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload);
|
||||
|
||||
ErrorCode importServiceFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData);
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
CaptchaInfo &captchaInfo);
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
@@ -60,6 +68,8 @@ public:
|
||||
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
void restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config);
|
||||
|
||||
ErrorCode deactivateDevice(const QString &serverId);
|
||||
|
||||
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
||||
@@ -98,6 +108,11 @@ public:
|
||||
AppStoreRestoreResult processAppStoreRestore(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol);
|
||||
|
||||
ErrorCode resolveImportServiceCaptcha(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const ProtocolData &protocolData,
|
||||
const QString &captchaId, const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut = nullptr);
|
||||
|
||||
private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(const QString &serverId) const;
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "version.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
@@ -30,6 +28,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
||||
m_vpnConnection(vpnConnection)
|
||||
{
|
||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||
connect(m_vpnConnection, &VpnConnection::serverSwitchFailed, this, &ConnectionController::serverSwitchFailed);
|
||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||
@@ -51,14 +50,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -67,13 +144,15 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
bool isApiConfig = false;
|
||||
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
const QString primaryDns = m_appSettingsRepository->primaryDns();
|
||||
const QString secondaryDns = m_appSettingsRepository->secondaryDns();
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
dns = cfg->getDnsPair(m_appSettingsRepository->useAmneziaDns(), primaryDns, secondaryDns);
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -83,7 +162,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -93,7 +172,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
break;
|
||||
@@ -105,7 +184,7 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
if (!cfg.has_value()) return ErrorCode::InternalError;
|
||||
container = cfg->defaultContainer;
|
||||
containerConfigModel = cfg->containerConfig(container);
|
||||
dns = { cfg->dns1, cfg->dns2 };
|
||||
dns = cfg->getDnsPair(primaryDns, secondaryDns);
|
||||
hostName = cfg->hostName;
|
||||
description = cfg->description;
|
||||
configVersion = serverConfigUtils::ConfigSource::AmneziaGateway;
|
||||
@@ -120,20 +199,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
if (dns.first.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.first)) {
|
||||
if (m_appSettingsRepository->useAmneziaDns()) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
} else {
|
||||
dns.first = m_appSettingsRepository->primaryDns();
|
||||
}
|
||||
}
|
||||
if (dns.second.isEmpty() || !NetworkUtilities::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_appSettingsRepository->secondaryDns();
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -67,12 +69,15 @@ signals:
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
void serverSwitchFailed();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -178,7 +178,8 @@ void CoreController::initControllers()
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel,
|
||||
#endif
|
||||
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel, this);
|
||||
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel,
|
||||
m_connectionController, this);
|
||||
setQmlContextProperty("InstallController", m_installUiController);
|
||||
|
||||
m_importController = new ImportUiController(m_importCoreController, this);
|
||||
@@ -190,7 +191,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
@@ -212,15 +213,16 @@ void CoreController::initControllers()
|
||||
setQmlContextProperty("SystemController", m_systemController);
|
||||
|
||||
m_networkReachabilityController = new NetworkReachabilityController(this);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
setQmlContextProperty("NetworkReachabilityController", m_networkReachabilityController);
|
||||
setQmlContextProperty("NetworkReachability", m_networkReachabilityController);
|
||||
|
||||
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
|
||||
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);
|
||||
|
||||
m_subscriptionUiController = new SubscriptionUiController(m_serversController, m_apiServicesModel, m_servicesCatalogController, m_subscriptionController,
|
||||
m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_apiAccountInfoModel,
|
||||
m_apiCountryModel, m_apiDevicesModel, m_settingsController, this);
|
||||
m_apiCountryModel, m_apiDevicesModel, m_settingsController,
|
||||
m_connectionController, this);
|
||||
setQmlContextProperty("SubscriptionUiController", m_subscriptionUiController);
|
||||
|
||||
m_apiNewsUiController = new ApiNewsUiController(m_newsModel, m_newsController, this);
|
||||
@@ -342,9 +344,6 @@ void CoreController::openConnectionByIndex(int serverIndex)
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
}
|
||||
if (m_serversController) {
|
||||
m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
|
||||
@@ -82,33 +82,11 @@
|
||||
#endif
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
class TestAdminSelfHostedExport;
|
||||
class TestServerEdit;
|
||||
class TestDefaultServerChange;
|
||||
class TestServerEdgeCases;
|
||||
class TestSignalOrder;
|
||||
class TestServersModelSync;
|
||||
class TestComplexOperations;
|
||||
class TestSettingsSignals;
|
||||
class TestUiServersModelAndController;
|
||||
class TestSelfHostedServerSetup;
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class CoreSignalHandlers;
|
||||
friend class TestMultipleImports;
|
||||
friend class TestAdminSelfHostedExport;
|
||||
friend class TestServerEdit;
|
||||
friend class TestDefaultServerChange;
|
||||
friend class TestServerEdgeCases;
|
||||
friend class TestSignalOrder;
|
||||
friend class TestServersModelSync;
|
||||
friend class TestComplexOperations;
|
||||
friend class TestSettingsSignals;
|
||||
friend class TestUiServersModelAndController;
|
||||
friend class TestSelfHostedServerSetup;
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
@@ -125,6 +103,36 @@ signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
protected:
|
||||
SecureServersRepository* serversRepositoryProtected() const { return m_serversRepository; }
|
||||
SecureAppSettingsRepository* appSettingsRepositoryProtected() const { return m_appSettingsRepository; }
|
||||
ServersModel* serversModelProtected() const { return m_serversModel; }
|
||||
ContainersModel* containersModelProtected() const { return m_containersModel; }
|
||||
ApiServicesModel* apiServicesModelProtected() const { return m_apiServicesModel; }
|
||||
NewsModel* newsModelProtected() const { return m_newsModel; }
|
||||
AllowedDnsModel* allowedDnsModelProtected() const { return m_allowedDnsModel; }
|
||||
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
|
||||
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
|
||||
LanguageModel* languageModelProtected() const { return m_languageModel; }
|
||||
|
||||
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
|
||||
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
|
||||
ExportController* exportControllerProtected() const { return m_exportController; }
|
||||
InstallController* installControllerProtected() const { return m_installController; }
|
||||
ServersController* serversControllerProtected() const { return m_serversController; }
|
||||
SettingsUiController* settingsUiControllerProtected() const { return m_settingsUiController; }
|
||||
SettingsController* settingsControllerProtected() const { return m_settingsController; }
|
||||
AllowedDnsUiController* allowedDnsUiControllerProtected() const { return m_allowedDnsUiController; }
|
||||
AllowedDnsController* allowedDnsControllerProtected() const { return m_allowedDnsController; }
|
||||
LanguageUiController* languageUiControllerProtected() const { return m_languageUiController; }
|
||||
IpSplitTunnelingController* ipSplitTunnelingControllerProtected() const { return m_ipSplitTunnelingController; }
|
||||
IpSplitTunnelingUiController* ipSplitTunnelingUiControllerProtected() const { return m_ipSplitTunnelingUiController; }
|
||||
AppSplitTunnelingController* appSplitTunnelingControllerProtected() const { return m_appSplitTunnelingController; }
|
||||
AppSplitTunnelingUiController* appSplitTunnelingUiControllerProtected() const { return m_appSplitTunnelingUiController; }
|
||||
ServersUiController* serversUiControllerProtected() const { return m_serversUiController; }
|
||||
ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; }
|
||||
ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; }
|
||||
|
||||
private:
|
||||
void initRepositories();
|
||||
void initCoreControllers();
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -95,6 +94,12 @@ void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::serverSwitchFailed, this, [this]() {
|
||||
m_coreController->m_subscriptionUiController->revertLastCountryChange();
|
||||
emit m_coreController->m_pageController->showNotificationMessage(
|
||||
tr("Failed to switch server. Existing connection maintained."));
|
||||
});
|
||||
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
|
||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||
|
||||
@@ -125,9 +130,9 @@ void CoreSignalHandlers::initInstallControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::serverIsBusy, m_coreController->m_installUiController, &InstallUiController::serverIsBusy);
|
||||
connect(m_coreController->m_installUiController, &InstallUiController::cancelInstallation, m_coreController->m_installController, &InstallController::cancelInstallation);
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIndexChanged,
|
||||
m_coreController->m_installUiController, [this](int serverIndex) {
|
||||
if (serverIndex >= 0) {
|
||||
connect(m_coreController->m_serversUiController, &ServersUiController::processedServerIdChanged,
|
||||
m_coreController->m_installUiController, [this](const QString &serverId) {
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_installUiController->clearProcessedServerCredentials();
|
||||
}
|
||||
});
|
||||
@@ -156,15 +161,17 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,17 +183,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,13 +241,16 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +355,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace
|
||||
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||
constexpr QLatin1String errorResponsePatternQrSessionNotFound("QR session not found");
|
||||
constexpr QLatin1String errorResponsePatternSessionNotFound("Session not found");
|
||||
|
||||
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||
|
||||
@@ -37,6 +39,7 @@ namespace
|
||||
constexpr int httpStatusCodeConflict = 409;
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
constexpr int httpStatusCodePaymentRequired = 402;
|
||||
constexpr int httpStatusCodeRequestTimeout = 408;
|
||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
@@ -76,7 +79,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QString(), QStringList { ip });
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
||||
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
||||
});
|
||||
@@ -206,8 +209,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
const auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, responseBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -217,7 +221,6 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -256,7 +259,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||
decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->addResult(qMakePair(errorCode, decryptionResult.decryptedBody));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
@@ -459,15 +462,19 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeRequestTimeout) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotFound) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
|| responseBody.contains(errorResponsePattern3) || responseBody.contains(errorResponsePatternQrSessionNotFound)
|
||||
|| responseBody.contains(errorResponsePatternSessionNotFound)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
|
||||
@@ -486,7 +486,7 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = configKey::amneziaOpenvpn;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)");
|
||||
QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data);
|
||||
@@ -645,7 +645,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
QJsonObject config;
|
||||
config[configKey::containers] = arr;
|
||||
config[configKey::defaultContainer] = containerName;
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
const static QRegularExpression dnsRegExp(
|
||||
"DNS = "
|
||||
@@ -699,7 +699,7 @@ QJsonObject ImportController::extractXrayConfig(const QString &data, ConfigTypes
|
||||
? configKey::amneziaSsxray
|
||||
: configKey::amneziaXray;
|
||||
if (description.isEmpty()) {
|
||||
config[configKey::description] = m_appSettingsRepository->nextAvailableServerName();
|
||||
config[configKey::description] = m_serversRepository->nextAvailableServerName();
|
||||
} else {
|
||||
config[configKey::description] = description;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "core/installers/sftpInstaller.h"
|
||||
#include "core/installers/socks5Installer.h"
|
||||
#include "core/installers/mtProxyInstaller.h"
|
||||
#include "core/configurators/xrayConfigurator.h"
|
||||
#include "core/installers/telemtInstaller.h"
|
||||
#include "core/installers/torInstaller.h"
|
||||
#include "core/installers/wireguardInstaller.h"
|
||||
@@ -71,6 +72,16 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -119,9 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -146,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -179,7 +191,17 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||
if (oldXrayConfig && newXrayConfig) {
|
||||
xrayServerSettingsChanged =
|
||||
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
if (reinstallRequired) {
|
||||
@@ -191,6 +213,21 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
}
|
||||
}
|
||||
|
||||
const bool skipXrayInboundSync =
|
||||
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
|
||||
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
@@ -205,6 +242,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -216,9 +288,9 @@ void InstallController::clearCachedProfile(const QString &serverId, DockerContai
|
||||
return;
|
||||
}
|
||||
|
||||
adminConfig->clearCachedClientProfile(container);
|
||||
const ContainerConfig containerConfigModel = adminConfig->containerConfig(container);
|
||||
|
||||
adminConfig->clearCachedClientProfile(container);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
|
||||
emit clientRevocationRequested(serverId, containerConfigModel, container);
|
||||
@@ -327,7 +399,7 @@ void InstallController::addEmptyServer(const ServerCredentials &credentials)
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
serverConfig.displayName = serverConfig.description.isEmpty() ? serverConfig.hostName : serverConfig.description;
|
||||
serverConfig.defaultContainer = DockerContainer::None;
|
||||
|
||||
@@ -638,12 +710,19 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
const auto* oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||
const auto* newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||
|
||||
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||
|
||||
if (oldXrayConfig && newXrayConfig) {
|
||||
if (oldXrayConfig->serverConfig.port != newXrayConfig->serverConfig.port)
|
||||
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
|
||||
? QString(protocols::xray::defaultPort)
|
||||
: oldXrayConfig->serverConfig.port;
|
||||
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
|
||||
? QString(protocols::xray::defaultPort)
|
||||
: newXrayConfig->serverConfig.port;
|
||||
if (oldPort != newPort) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,8 +836,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = regex.match(stdOut);
|
||||
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
|
||||
if (match.hasMatch()) {
|
||||
int majorVersion = match.captured(1).toInt();
|
||||
int minorVersion = match.captured(2).toInt();
|
||||
@@ -771,8 +850,19 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
|
||||
if (stdOut.contains("lock"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("command not found"))
|
||||
if (stdOut.contains("Container runtime is not supported"))
|
||||
return ErrorCode::ServerContainerRuntimeNotSupported;
|
||||
|
||||
QRegularExpression notFoundRegex(
|
||||
R"(^.*(?:sudo:|docker:).*not found.*$)",
|
||||
QRegularExpression::MultilineOption);
|
||||
|
||||
if (notFoundRegex.match(stdOut).hasMatch()) {
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
}
|
||||
|
||||
if (stdOut.contains("Container runtime service not running"))
|
||||
return ErrorCode::ContainerRuntimeServiceNotRunning;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -809,7 +899,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
|
||||
return ErrorCode::ServerUserNotInSudo;
|
||||
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
@@ -942,10 +1032,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString())));
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1130,7 +1221,7 @@ ErrorCode InstallController::installServer(const ServerCredentials &credentials,
|
||||
serverConfig.userName = credentials.userName;
|
||||
serverConfig.password = credentials.secretData;
|
||||
serverConfig.port = credentials.port;
|
||||
serverConfig.description = m_appSettingsRepository->nextAvailableServerName();
|
||||
serverConfig.description = m_serversRepository->nextAvailableServerName();
|
||||
|
||||
for (auto iterator = preparedContainers.begin(); iterator != preparedContainers.end(); iterator++) {
|
||||
serverConfig.containers.insert(iterator.key(), iterator.value());
|
||||
@@ -1200,28 +1291,26 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::checkSshConnection(const ServerCredentials &credentials, QString &output,
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
ServerCredentials processedCredentials = credentials;
|
||||
|
||||
if (processedCredentials.secretData.contains("BEGIN") && processedCredentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
if (!passphraseCallback) {
|
||||
return ErrorCode::SshPrivateKeyError;
|
||||
}
|
||||
|
||||
QString decryptedPrivateKey;
|
||||
errorCode = sshSession.getDecryptedPrivateKey(processedCredentials, decryptedPrivateKey, passphraseCallback);
|
||||
errorCode = sshSession.getDecryptedPrivateKey(credentials, decryptedPrivateKey, passphraseCallback);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
processedCredentials.secretData = decryptedPrivateKey;
|
||||
credentials.secretData = decryptedPrivateKey;
|
||||
}
|
||||
|
||||
output = sshSession.checkSshConnection(processedCredentials, errorCode);
|
||||
output = sshSession.checkSshConnection(credentials, errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -1425,7 +1514,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1450,7 +1539,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,12 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
@@ -64,7 +69,8 @@ public:
|
||||
|
||||
bool isUpdateDockerContainerRequired(DockerContainer container, const ContainerConfig &oldConfig, const ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode checkSshConnection(const ServerCredentials &credentials, QString &output, std::function<QString()> passphraseCallback = nullptr);
|
||||
ErrorCode checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback = nullptr);
|
||||
|
||||
bool isServerAlreadyExists(const ServerCredentials &credentials, int &existingServerIndex);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -51,6 +52,7 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -58,6 +60,7 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -67,6 +70,7 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->name = name;
|
||||
cfg->displayName = name;
|
||||
cfg->nameOverriddenByUser = true;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
|
||||
@@ -217,6 +217,11 @@ void SettingsController::toggleAutoStart(bool enable)
|
||||
|
||||
bool SettingsController::isStartMinimizedEnabled() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (!isAutoStartEnabled()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return m_appSettingsRepository->isStartMinimized();
|
||||
}
|
||||
|
||||
@@ -358,6 +363,6 @@ void SettingsController::disablePremV1MigrationReminder()
|
||||
|
||||
QString SettingsController::nextAvailableServerName() const
|
||||
{
|
||||
return m_appSettingsRepository->nextAvailableServerName();
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
namespace
|
||||
@@ -23,7 +24,7 @@ namespace
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_windows_x64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos_x64.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
@@ -109,7 +110,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
logger.error() << "Gateway request failed, error code:" << static_cast<int>(err);
|
||||
finishUpdateCheck();
|
||||
return;
|
||||
}
|
||||
@@ -184,7 +185,7 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
@@ -196,21 +197,13 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
@@ -220,7 +213,7 @@ QString UpdateController::composeDownloadUrl() const
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
@@ -252,23 +245,15 @@ void UpdateController::runInstaller()
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
#endif
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << QString::fromUtf8(reply->readAll());
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
logger.error() << "Installer download failed, network error:" << static_cast<int>(reply->error())
|
||||
<< reply->errorString();
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
@@ -292,7 +277,7 @@ int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#include "socks5Installer.h"
|
||||
|
||||
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
|
||||
@@ -33,10 +35,29 @@ ContainerConfig Socks5Installer::generateConfig(DockerContainer container, int p
|
||||
ErrorCode Socks5Installer::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
SshSession* sshSession, ContainerConfig &config)
|
||||
{
|
||||
Q_UNUSED(container);
|
||||
Q_UNUSED(credentials);
|
||||
Q_UNUSED(sshSession);
|
||||
Q_UNUSED(config);
|
||||
if (container != DockerContainer::Socks5Proxy || !sshSession) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
Socks5ProxyProtocolConfig *socks5Config = config.getSocks5ProxyProtocolConfig();
|
||||
if (!socks5Config) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode readError = ErrorCode::NoError;
|
||||
const QByteArray configRaw = sshSession->getTextFileFromContainer(
|
||||
container, credentials, QString::fromUtf8(protocols::socks5Proxy::proxyConfigPath), readError);
|
||||
if (readError != ErrorCode::NoError || configRaw.trimmed().isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString proxyConfig = QString::fromUtf8(configRaw);
|
||||
static const QRegularExpression usernameAndPasswordRegExp(QStringLiteral("users (\\w+):CL:(\\w+)"));
|
||||
const QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig);
|
||||
if (usernameAndPasswordMatch.hasMatch()) {
|
||||
socks5Config->userName = usernameAndPasswordMatch.captured(1);
|
||||
socks5Config->password = usernameAndPasswordMatch.captured(2);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/models/api/apiConfig.h"
|
||||
#include "core/models/api/authData.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -67,6 +68,20 @@ ContainerConfig ApiV2ServerConfig::containerConfig(DockerContainer container) co
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
QPair<QString, QString> ApiV2ServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject ApiV2ServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -80,9 +95,6 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
|
||||
obj[configKey::configVersion] = configVersion;
|
||||
|
||||
@@ -134,7 +146,6 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.nameOverriddenByUser = json.value(configKey::nameOverriddenByUser).toBool(false);
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.configVersion = json.value(configKey::configVersion).toInt(2);
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
@@ -43,6 +44,9 @@ struct ApiV2ServerConfig {
|
||||
bool isExternalPremium() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static ApiV2ServerConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
@@ -23,9 +23,7 @@ LegacyApiServerConfig LegacyApiServerConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
LegacyApiServerConfig config;
|
||||
|
||||
config.name = json.value(configKey::name).toString();
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
@@ -108,35 +108,114 @@ QJsonObject XrayXhttpConfig::toJson() const
|
||||
return obj;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
XrayXhttpConfig clearedXhttpConfig()
|
||||
{
|
||||
XrayXhttpConfig c;
|
||||
c.mode = QString();
|
||||
c.host = QString();
|
||||
c.path = QString();
|
||||
c.headersTemplate = QString();
|
||||
c.uplinkMethod = QString();
|
||||
c.disableGrpc = false;
|
||||
c.disableSse = false;
|
||||
c.sessionPlacement = QString();
|
||||
c.sessionKey = QString();
|
||||
c.seqPlacement = QString();
|
||||
c.seqKey = QString();
|
||||
c.uplinkDataPlacement = QString();
|
||||
c.uplinkDataKey = QString();
|
||||
c.uplinkChunkSize = QString();
|
||||
c.scMaxBufferedPosts = QString();
|
||||
c.scMaxEachPostBytesMin = QString();
|
||||
c.scMaxEachPostBytesMax = QString();
|
||||
c.scMinPostsIntervalMsMin = QString();
|
||||
c.scMinPostsIntervalMsMax = QString();
|
||||
c.scStreamUpServerSecsMin = QString();
|
||||
c.scStreamUpServerSecsMax = QString();
|
||||
return c;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
XrayXhttpConfig c;
|
||||
c.mode = json.value(configKey::xhttpMode).toString(protocols::xray::defaultXhttpMode);
|
||||
c.host = json.value(configKey::xhttpHost).toString(protocols::xray::defaultSite);
|
||||
c.path = json.value(configKey::xhttpPath).toString();
|
||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString(protocols::xray::defaultXhttpHeadersTemplate);
|
||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString(protocols::xray::defaultXhttpUplinkMethod);
|
||||
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool(true);
|
||||
c.disableSse = json.value(configKey::xhttpDisableSse).toBool(true);
|
||||
if (json.isEmpty()) {
|
||||
return clearedXhttpConfig();
|
||||
}
|
||||
|
||||
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
||||
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
|
||||
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString(protocols::xray::defaultXhttpSessionPlacement);
|
||||
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
|
||||
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString(protocols::xray::defaultXhttpUplinkDataPlacement);
|
||||
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
|
||||
XrayXhttpConfig c = clearedXhttpConfig();
|
||||
|
||||
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString("0");
|
||||
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
|
||||
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString("1");
|
||||
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString("100");
|
||||
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString("100");
|
||||
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString("800");
|
||||
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString("1");
|
||||
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString("100");
|
||||
if (json.contains(configKey::xhttpMode)) {
|
||||
c.mode = json.value(configKey::xhttpMode).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpHost)) {
|
||||
c.host = json.value(configKey::xhttpHost).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpPath)) {
|
||||
c.path = json.value(configKey::xhttpPath).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpHeadersTemplate)) {
|
||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpUplinkMethod)) {
|
||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpDisableGrpc)) {
|
||||
c.disableGrpc = json.value(configKey::xhttpDisableGrpc).toBool();
|
||||
}
|
||||
if (json.contains(configKey::xhttpDisableSse)) {
|
||||
c.disableSse = json.value(configKey::xhttpDisableSse).toBool();
|
||||
}
|
||||
if (json.contains(configKey::xhttpSessionPlacement)) {
|
||||
c.sessionPlacement = json.value(configKey::xhttpSessionPlacement).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpSessionKey)) {
|
||||
c.sessionKey = json.value(configKey::xhttpSessionKey).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpSeqPlacement)) {
|
||||
c.seqPlacement = json.value(configKey::xhttpSeqPlacement).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpSeqKey)) {
|
||||
c.seqKey = json.value(configKey::xhttpSeqKey).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpUplinkDataPlacement)) {
|
||||
c.uplinkDataPlacement = json.value(configKey::xhttpUplinkDataPlacement).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpUplinkDataKey)) {
|
||||
c.uplinkDataKey = json.value(configKey::xhttpUplinkDataKey).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpUplinkChunkSize)) {
|
||||
c.uplinkChunkSize = json.value(configKey::xhttpUplinkChunkSize).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScMaxBufferedPosts)) {
|
||||
c.scMaxBufferedPosts = json.value(configKey::xhttpScMaxBufferedPosts).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScMaxEachPostBytesMin)) {
|
||||
c.scMaxEachPostBytesMin = json.value(configKey::xhttpScMaxEachPostBytesMin).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScMaxEachPostBytesMax)) {
|
||||
c.scMaxEachPostBytesMax = json.value(configKey::xhttpScMaxEachPostBytesMax).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScMinPostsIntervalMsMin)) {
|
||||
c.scMinPostsIntervalMsMin = json.value(configKey::xhttpScMinPostsIntervalMsMin).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScMinPostsIntervalMsMax)) {
|
||||
c.scMinPostsIntervalMsMax = json.value(configKey::xhttpScMinPostsIntervalMsMax).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScStreamUpServerSecsMin)) {
|
||||
c.scStreamUpServerSecsMin = json.value(configKey::xhttpScStreamUpServerSecsMin).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpScStreamUpServerSecsMax)) {
|
||||
c.scStreamUpServerSecsMax = json.value(configKey::xhttpScStreamUpServerSecsMax).toString();
|
||||
}
|
||||
|
||||
c.xPadding = XrayXPaddingConfig::fromJson(json.value("xPadding").toObject());
|
||||
c.xmux = XrayXmuxConfig::fromJson(json.value("xmux").toObject());
|
||||
if (json.contains(QLatin1String("xPadding"))) {
|
||||
c.xPadding = XrayXPaddingConfig::fromJson(json.value(QLatin1String("xPadding")).toObject());
|
||||
}
|
||||
if (json.contains(QLatin1String("xmux"))) {
|
||||
c.xmux = XrayXmuxConfig::fromJson(json.value(QLatin1String("xmux")).toObject());
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -156,12 +235,27 @@ QJsonObject XrayMkcpConfig::toJson() const
|
||||
XrayMkcpConfig XrayMkcpConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
XrayMkcpConfig c;
|
||||
c.tti = json.value(configKey::mkcpTti).toString();
|
||||
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
|
||||
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
|
||||
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
|
||||
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
|
||||
c.congestion = json.value(configKey::mkcpCongestion).toBool(true);
|
||||
if (json.isEmpty()) {
|
||||
return c;
|
||||
}
|
||||
if (json.contains(configKey::mkcpTti)) {
|
||||
c.tti = json.value(configKey::mkcpTti).toString();
|
||||
}
|
||||
if (json.contains(configKey::mkcpUplinkCapacity)) {
|
||||
c.uplinkCapacity = json.value(configKey::mkcpUplinkCapacity).toString();
|
||||
}
|
||||
if (json.contains(configKey::mkcpDownlinkCapacity)) {
|
||||
c.downlinkCapacity = json.value(configKey::mkcpDownlinkCapacity).toString();
|
||||
}
|
||||
if (json.contains(configKey::mkcpReadBufferSize)) {
|
||||
c.readBufferSize = json.value(configKey::mkcpReadBufferSize).toString();
|
||||
}
|
||||
if (json.contains(configKey::mkcpWriteBufferSize)) {
|
||||
c.writeBufferSize = json.value(configKey::mkcpWriteBufferSize).toString();
|
||||
}
|
||||
if (json.contains(configKey::mkcpCongestion)) {
|
||||
c.congestion = json.value(configKey::mkcpCongestion).toBool();
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -208,8 +302,14 @@ QJsonObject XrayServerConfig::toJson() const
|
||||
if (!transport.isEmpty()) {
|
||||
obj[configKey::xrayTransport] = transport;
|
||||
}
|
||||
obj["xhttp"] = xhttp.toJson();
|
||||
obj["mkcp"] = mkcp.toJson();
|
||||
const QJsonObject xhttpObj = xhttp.toJson();
|
||||
if (!xhttpObj.isEmpty()) {
|
||||
obj[QStringLiteral("xhttp")] = xhttpObj;
|
||||
}
|
||||
const QJsonObject mkcpObj = mkcp.toJson();
|
||||
if (!mkcpObj.isEmpty()) {
|
||||
obj[QStringLiteral("mkcp")] = mkcpObj;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -225,20 +325,39 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
|
||||
c.site = json.value(configKey::site).toString();
|
||||
c.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
||||
|
||||
// New: Security
|
||||
c.security = json.value(configKey::xraySecurity).toString(protocols::xray::defaultSecurity);
|
||||
c.flow = json.value(configKey::xrayFlow).toString(protocols::xray::defaultFlow);
|
||||
c.fingerprint = json.value(configKey::xrayFingerprint).toString(protocols::xray::defaultFingerprint);
|
||||
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||
if (json.contains(configKey::xraySecurity)) {
|
||||
c.security = json.value(configKey::xraySecurity).toString();
|
||||
}
|
||||
if (json.contains(configKey::xrayFlow)) {
|
||||
c.flow = json.value(configKey::xrayFlow).toString();
|
||||
}
|
||||
if (json.contains(configKey::xrayFingerprint)) {
|
||||
c.fingerprint = json.value(configKey::xrayFingerprint).toString();
|
||||
if (c.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
|
||||
c.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
|
||||
}
|
||||
}
|
||||
if (json.contains(configKey::xraySni)) {
|
||||
c.sni = json.value(configKey::xraySni).toString();
|
||||
}
|
||||
if (json.contains(configKey::xrayAlpn)) {
|
||||
c.alpn = json.value(configKey::xrayAlpn).toString();
|
||||
}
|
||||
if (json.contains(configKey::xrayTransport)) {
|
||||
c.transport = json.value(configKey::xrayTransport).toString();
|
||||
}
|
||||
if (json.contains(QLatin1String("xhttp"))) {
|
||||
const QJsonObject xhttpJson = json.value(QLatin1String("xhttp")).toObject();
|
||||
if (!xhttpJson.isEmpty()) {
|
||||
c.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
|
||||
}
|
||||
}
|
||||
if (json.contains(QLatin1String("mkcp"))) {
|
||||
const QJsonObject mkcpJson = json.value(QLatin1String("mkcp")).toObject();
|
||||
if (!mkcpJson.isEmpty()) {
|
||||
c.mkcp = XrayMkcpConfig::fromJson(mkcpJson);
|
||||
}
|
||||
}
|
||||
c.sni = json.value(configKey::xraySni).toString(protocols::xray::defaultSni);
|
||||
c.alpn = json.value(configKey::xrayAlpn).toString(protocols::xray::defaultAlpn);
|
||||
|
||||
// New: Transport
|
||||
c.transport = json.value(configKey::xrayTransport).toString(protocols::xray::defaultTransport);
|
||||
c.xhttp = XrayXhttpConfig::fromJson(json.value("xhttp").toObject());
|
||||
c.mkcp = XrayMkcpConfig::fromJson(json.value("mkcp").toObject());
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -251,7 +370,10 @@ bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) con
|
||||
&& flow == other.flow
|
||||
&& transport == other.transport
|
||||
&& fingerprint == other.fingerprint
|
||||
&& sni == other.sni;
|
||||
&& sni == other.sni
|
||||
&& alpn == other.alpn
|
||||
&& xhttp.toJson() == other.xhttp.toJson()
|
||||
&& mkcp.toJson() == other.mkcp.toJson();
|
||||
}
|
||||
|
||||
QJsonObject XrayClientConfig::toJson() const
|
||||
@@ -351,9 +473,154 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
|
||||
}
|
||||
}
|
||||
|
||||
c.needsClientHydration =
|
||||
c.hasClientConfig()
|
||||
&& (!json.contains(configKey::xrayTransport) || c.serverConfig.isThirdPartyConfig);
|
||||
if (c.needsClientHydration) {
|
||||
c.hydrateServerConfigFromClientNative();
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
bool XrayProtocolConfig::hydrateServerConfigFromClientNative()
|
||||
{
|
||||
if (!clientConfig.has_value() || clientConfig->nativeConfig.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(clientConfig->nativeConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonObject root = doc.object();
|
||||
const QJsonArray outbounds = root.value(protocols::xray::outbounds).toArray();
|
||||
if (outbounds.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonObject outbound = outbounds[0].toObject();
|
||||
const QJsonObject streamSettings = outbound.value(protocols::xray::streamSettings).toObject();
|
||||
if (streamSettings.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XrayServerConfig &srv = serverConfig;
|
||||
|
||||
const QJsonObject settings = outbound.value(protocols::xray::settings).toObject();
|
||||
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
|
||||
if (!vnext.isEmpty()) {
|
||||
const QJsonObject vnextEntry = vnext[0].toObject();
|
||||
if (vnextEntry.contains(protocols::xray::port)) {
|
||||
srv.port = QString::number(vnextEntry.value(protocols::xray::port).toInt());
|
||||
}
|
||||
const QJsonArray users = vnextEntry.value(protocols::xray::users).toArray();
|
||||
if (!users.isEmpty()) {
|
||||
srv.flow = users[0].toObject().value(protocols::xray::flow).toString();
|
||||
}
|
||||
}
|
||||
|
||||
const QString networkVal = streamSettings.value(protocols::xray::network).toString(QStringLiteral("tcp"));
|
||||
if (networkVal == QLatin1String("xhttp")) {
|
||||
srv.transport = QStringLiteral("xhttp");
|
||||
} else if (networkVal == QLatin1String("kcp")) {
|
||||
srv.transport = QStringLiteral("mkcp");
|
||||
} else {
|
||||
srv.transport = QStringLiteral("raw");
|
||||
}
|
||||
|
||||
if (streamSettings.contains(protocols::xray::security)) {
|
||||
srv.security = streamSettings.value(protocols::xray::security).toString();
|
||||
}
|
||||
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
const QJsonObject rs = streamSettings.value(protocols::xray::realitySettings).toObject();
|
||||
srv.sni = rs.value(protocols::xray::serverName).toString();
|
||||
srv.site = srv.sni.isEmpty() ? srv.site : srv.sni;
|
||||
const QString fp = rs.value(protocols::xray::fingerprint).toString();
|
||||
if (!fp.isEmpty()) {
|
||||
srv.fingerprint = fp.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)
|
||||
? QString::fromLatin1(protocols::xray::defaultFingerprint)
|
||||
: fp;
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.security == QLatin1String("tls")) {
|
||||
const QJsonObject tls = streamSettings.value(QStringLiteral("tlsSettings")).toObject();
|
||||
srv.sni = tls.value(protocols::xray::serverName).toString();
|
||||
const QString fp = tls.value(protocols::xray::fingerprint).toString();
|
||||
if (!fp.isEmpty()) {
|
||||
srv.fingerprint = fp;
|
||||
}
|
||||
QStringList alpnList;
|
||||
for (const QJsonValue &v : tls.value(QStringLiteral("alpn")).toArray()) {
|
||||
alpnList << v.toString();
|
||||
}
|
||||
if (!alpnList.isEmpty()) {
|
||||
srv.alpn = alpnList.join(QLatin1Char(','));
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.transport == QLatin1String("xhttp")) {
|
||||
const QJsonObject xhttpObj = streamSettings.value(QStringLiteral("xhttpSettings")).toObject();
|
||||
QJsonObject xhttpJson;
|
||||
const QString mode = xhttpObj.value(QStringLiteral("mode")).toString();
|
||||
if (!mode.isEmpty()) {
|
||||
if (mode == QLatin1String("auto")) {
|
||||
xhttpJson[configKey::xhttpMode] = QStringLiteral("Auto");
|
||||
} else if (mode == QLatin1String("packet-up")) {
|
||||
xhttpJson[configKey::xhttpMode] = QStringLiteral("Packet-up");
|
||||
} else if (mode == QLatin1String("stream-up")) {
|
||||
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-up");
|
||||
} else if (mode == QLatin1String("stream-one")) {
|
||||
xhttpJson[configKey::xhttpMode] = QStringLiteral("Stream-one");
|
||||
} else {
|
||||
xhttpJson[configKey::xhttpMode] = mode;
|
||||
}
|
||||
}
|
||||
if (xhttpObj.contains(QStringLiteral("host"))) {
|
||||
xhttpJson[configKey::xhttpHost] = xhttpObj.value(QStringLiteral("host")).toString();
|
||||
}
|
||||
if (xhttpObj.contains(QStringLiteral("path"))) {
|
||||
xhttpJson[configKey::xhttpPath] = xhttpObj.value(QStringLiteral("path")).toString();
|
||||
}
|
||||
if (xhttpObj.contains(QStringLiteral("uplinkHTTPMethod"))) {
|
||||
xhttpJson[configKey::xhttpUplinkMethod] = xhttpObj.value(QStringLiteral("uplinkHTTPMethod")).toString();
|
||||
}
|
||||
xhttpJson[configKey::xhttpDisableGrpc] = xhttpObj.value(QStringLiteral("noGRPCHeader")).toBool(true);
|
||||
xhttpJson[configKey::xhttpDisableSse] = xhttpObj.value(QStringLiteral("noSSEHeader")).toBool(true);
|
||||
srv.xhttp = XrayXhttpConfig::fromJson(xhttpJson);
|
||||
}
|
||||
|
||||
if (srv.transport == QLatin1String("mkcp")) {
|
||||
const QJsonObject kcpObj = streamSettings.value(QStringLiteral("kcpSettings")).toObject();
|
||||
XrayMkcpConfig mk;
|
||||
if (kcpObj.contains(QStringLiteral("tti"))) {
|
||||
mk.tti = QString::number(kcpObj.value(QStringLiteral("tti")).toInt());
|
||||
}
|
||||
if (kcpObj.contains(QStringLiteral("uplinkCapacity"))) {
|
||||
mk.uplinkCapacity = QString::number(kcpObj.value(QStringLiteral("uplinkCapacity")).toInt());
|
||||
}
|
||||
if (kcpObj.contains(QStringLiteral("downlinkCapacity"))) {
|
||||
mk.downlinkCapacity = QString::number(kcpObj.value(QStringLiteral("downlinkCapacity")).toInt());
|
||||
}
|
||||
if (kcpObj.contains(QStringLiteral("readBufferSize"))) {
|
||||
mk.readBufferSize = QString::number(kcpObj.value(QStringLiteral("readBufferSize")).toInt());
|
||||
}
|
||||
if (kcpObj.contains(QStringLiteral("writeBufferSize"))) {
|
||||
mk.writeBufferSize = QString::number(kcpObj.value(QStringLiteral("writeBufferSize")).toInt());
|
||||
}
|
||||
if (kcpObj.contains(QStringLiteral("congestion"))) {
|
||||
mk.congestion = kcpObj.value(QStringLiteral("congestion")).toBool(true);
|
||||
}
|
||||
srv.mkcp = mk;
|
||||
}
|
||||
|
||||
needsClientHydration = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XrayProtocolConfig::hasClientConfig() const
|
||||
{
|
||||
return clientConfig.has_value();
|
||||
|
||||
@@ -75,6 +75,7 @@ struct XrayXhttpConfig {
|
||||
XrayXmuxConfig xmux;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
/// Reads only keys present in JSON (no Amnezia UI defaults). Use XrayConfigModel::applyDefaultsToServerConfig for UI.
|
||||
static XrayXhttpConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
@@ -99,15 +100,13 @@ struct XrayServerConfig {
|
||||
QString site;
|
||||
bool isThirdPartyConfig = false;
|
||||
|
||||
// New: Security
|
||||
QString security = protocols::xray::defaultSecurity;
|
||||
QString flow = protocols::xray::defaultFlow;
|
||||
QString fingerprint = protocols::xray::defaultFingerprint;
|
||||
QString sni = protocols::xray::defaultSni;
|
||||
QString alpn = protocols::xray::defaultAlpn;
|
||||
QString security;
|
||||
QString flow;
|
||||
QString fingerprint;
|
||||
QString sni;
|
||||
QString alpn;
|
||||
|
||||
// New: Transport
|
||||
QString transport = protocols::xray::defaultTransport;
|
||||
QString transport;
|
||||
XrayXhttpConfig xhttp;
|
||||
XrayMkcpConfig mkcp;
|
||||
|
||||
@@ -139,6 +138,10 @@ struct XrayProtocolConfig {
|
||||
bool hasClientConfig() const;
|
||||
void setClientConfig(const XrayClientConfig &config);
|
||||
void clearClientConfig();
|
||||
|
||||
bool needsClientHydration = false;
|
||||
|
||||
bool hydrateServerConfigFromClientNative();
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -28,6 +29,25 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject NativeServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -35,9 +55,6 @@ QJsonObject NativeServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -70,7 +87,6 @@ NativeServerConfig NativeServerConfig::fromJson(const QJsonObject& json)
|
||||
NativeServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
@@ -25,6 +26,11 @@ struct NativeServerConfig {
|
||||
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static NativeServerConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
@@ -87,9 +87,6 @@ QJsonObject SelfHostedAdminServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -132,7 +129,6 @@ SelfHostedAdminServerConfig SelfHostedAdminServerConfig::fromJson(const QJsonObj
|
||||
SelfHostedAdminServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -42,6 +43,26 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
QString d2 = dns2;
|
||||
|
||||
if (d1.isEmpty() || !NetworkUtilities::checkIPv4Format(d1)) {
|
||||
d1 = primaryDns;
|
||||
}
|
||||
if (d2.isEmpty() || !NetworkUtilities::checkIPv4Format(d2)) {
|
||||
d2 = secondaryDns;
|
||||
}
|
||||
return { d1, d2 };
|
||||
}
|
||||
|
||||
QJsonObject SelfHostedUserServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -49,9 +70,6 @@ QJsonObject SelfHostedUserServerConfig::toJson() const
|
||||
if (!description.isEmpty()) {
|
||||
obj[configKey::description] = this->description;
|
||||
}
|
||||
if (!displayName.isEmpty()) {
|
||||
obj[configKey::displayName] = displayName;
|
||||
}
|
||||
if (!hostName.isEmpty()) {
|
||||
obj[configKey::hostName] = hostName;
|
||||
}
|
||||
@@ -84,7 +102,6 @@ SelfHostedUserServerConfig SelfHostedUserServerConfig::fromJson(const QJsonObjec
|
||||
SelfHostedUserServerConfig config;
|
||||
|
||||
config.description = json.value(configKey::description).toString();
|
||||
config.displayName = json.value(configKey::displayName).toString();
|
||||
config.hostName = json.value(configKey::hostName).toString();
|
||||
|
||||
QJsonArray containersArray = json.value(configKey::containers).toArray();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <optional>
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
@@ -30,6 +31,11 @@ struct SelfHostedUserServerConfig {
|
||||
std::optional<ServerCredentials> credentials() const;
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static SelfHostedUserServerConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
@@ -39,33 +39,35 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
cleanupResources();
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -168,27 +170,13 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString());
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
});
|
||||
if (res != ErrorCode::NoError) {
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Detect default gateway
|
||||
#ifdef Q_OS_MAC
|
||||
QProcess p;
|
||||
@@ -343,38 +331,9 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
|
||||
m_vpnGateway = l.split(" ").at(2);
|
||||
#ifdef Q_OS_WIN
|
||||
QThread::msleep(300);
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
||||
{
|
||||
// killSwitch toggle
|
||||
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
|
||||
}
|
||||
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
||||
m_configData.insert("vpnGateway", m_vpnGateway);
|
||||
m_configData.insert("vpnServer",
|
||||
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
|
||||
iface->enablePeerTraffic(m_configData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
// killSwitch toggle
|
||||
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
m_configData.insert("vpnServer",
|
||||
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
|
||||
}
|
||||
});
|
||||
}
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||
#endif
|
||||
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -106,6 +106,19 @@ QString VpnProtocol::vpnLocalAddress() const
|
||||
return m_vpnLocalAddress;
|
||||
}
|
||||
|
||||
bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container)
|
||||
{
|
||||
return container == amnezia::DockerContainer::Awg
|
||||
|| container == amnezia::DockerContainer::Awg2
|
||||
|| container == amnezia::DockerContainer::WireGuard;
|
||||
}
|
||||
|
||||
bool VpnProtocol::isXrayBased(amnezia::DockerContainer container)
|
||||
{
|
||||
return container == amnezia::DockerContainer::Xray
|
||||
|| container == amnezia::DockerContainer::SSXray;
|
||||
}
|
||||
|
||||
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
|
||||
{
|
||||
switch (container) {
|
||||
@@ -137,6 +150,7 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState)
|
||||
case Vpn::ConnectionState::Preparing: return tr("Preparing");
|
||||
case Vpn::ConnectionState::Connecting: return tr("Connecting...");
|
||||
case Vpn::ConnectionState::Connected: return tr("Connected");
|
||||
case Vpn::ConnectionState::Switching: return tr("Switching...");
|
||||
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
|
||||
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
|
||||
case Vpn::ConnectionState::Error: return tr("Error");
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Vpn
|
||||
Preparing,
|
||||
Connecting,
|
||||
Connected,
|
||||
Switching,
|
||||
Disconnecting,
|
||||
Reconnecting,
|
||||
Error
|
||||
@@ -60,6 +61,7 @@ public:
|
||||
virtual bool isDisconnected() const;
|
||||
virtual ErrorCode start() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void setPrimary(const QJsonObject& config) { Q_UNUSED(config) }
|
||||
|
||||
Vpn::ConnectionState connectionState() const;
|
||||
ErrorCode lastError() const;
|
||||
@@ -71,6 +73,8 @@ public:
|
||||
QString vpnLocalAddress() const;
|
||||
|
||||
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
|
||||
static bool isWireGuardBased(amnezia::DockerContainer container);
|
||||
static bool isXrayBased(amnezia::DockerContainer container);
|
||||
|
||||
signals:
|
||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||
@@ -78,6 +82,8 @@ signals:
|
||||
void timeoutTimerEvent();
|
||||
void protocolError(amnezia::ErrorCode e);
|
||||
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
|
||||
void primaryReady();
|
||||
void primaryFailed();
|
||||
|
||||
public slots:
|
||||
virtual void onTimeout(); // todo: remove?
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
|
||||
: VpnProtocol(configuration, parent)
|
||||
{
|
||||
m_impl.reset(new LocalSocketController());
|
||||
const QString ifname = configuration.value("ifname").toString();
|
||||
m_impl.reset(new LocalSocketController(ifname));
|
||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||
[this](const QString& pubkey, const QDateTime&) {
|
||||
Q_UNUSED(pubkey)
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
});
|
||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||
@@ -33,12 +35,18 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
||||
|
||||
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
|
||||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
|
||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||
}
|
||||
if (m_connectionState == Vpn::ConnectionState::Connected) {
|
||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||
connect(m_impl.get(), &ControllerImpl::primaryReady,
|
||||
this, &WireguardProtocol::primaryReady);
|
||||
connect(m_impl.get(), &ControllerImpl::primaryFailed,
|
||||
this, &WireguardProtocol::primaryFailed);
|
||||
m_impl->initialize(nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -48,13 +56,7 @@ WireguardProtocol::~WireguardProtocol()
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
stopMzImpl();
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
ErrorCode WireguardProtocol::start()
|
||||
{
|
||||
QString protocolName = m_rawConfig.value("protocol").toString();
|
||||
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
|
||||
@@ -62,18 +64,19 @@ ErrorCode WireguardProtocol::startMzImpl()
|
||||
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
|
||||
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
|
||||
|
||||
m_stopped = false;
|
||||
m_impl->activate(m_rawConfig);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::stopMzImpl()
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
if (m_stopped) return;
|
||||
m_stopped = true;
|
||||
m_impl->deactivate();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
ErrorCode WireguardProtocol::start()
|
||||
void WireguardProtocol::setPrimary(const QJsonObject& config)
|
||||
{
|
||||
return startMzImpl();
|
||||
m_impl->setPrimary(config);
|
||||
}
|
||||
|
||||
@@ -21,11 +21,10 @@ public:
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
ErrorCode startMzImpl();
|
||||
ErrorCode stopMzImpl();
|
||||
void setPrimary(const QJsonObject& config) override;
|
||||
|
||||
private:
|
||||
bool m_stopped = false;
|
||||
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
};
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
|
||||
#include <exception>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
static const QString tunName = "utun22";
|
||||
#else
|
||||
static const QString tunName = "tun2";
|
||||
#endif
|
||||
|
||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||
{
|
||||
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
||||
@@ -34,11 +28,16 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
|
||||
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
|
||||
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
|
||||
|
||||
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
|
||||
m_dnsServers.push_back(QHostAddress(primaryDns));
|
||||
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
|
||||
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString();
|
||||
m_dnsServers.push_back(QHostAddress(secondaryDns));
|
||||
m_tunName = configuration.value("tunName").toString();
|
||||
if (m_tunName.isEmpty()) {
|
||||
m_tunName = configuration.value("ifname").toString();
|
||||
}
|
||||
if (m_tunName.isEmpty()) {
|
||||
#ifdef Q_OS_MACOS
|
||||
m_tunName = QStringLiteral("utun22");
|
||||
#else
|
||||
m_tunName = QStringLiteral("tun2");
|
||||
#endif
|
||||
}
|
||||
|
||||
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
|
||||
@@ -68,6 +67,8 @@ ErrorCode XrayProtocol::start()
|
||||
{
|
||||
qDebug() << "XrayProtocol::start()";
|
||||
|
||||
m_phase = Phase::Active;
|
||||
|
||||
// Inject SOCKS5 auth into the inbound before starting xray.
|
||||
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
||||
amnezia::serialization::inbounds::InboundCredentials creds;
|
||||
@@ -106,7 +107,7 @@ ErrorCode XrayProtocol::start()
|
||||
|
||||
return IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
auto xrayStart = iface->xrayStart(m_tunName, xrayConfigStr);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
qCritical() << "Failed to start xray";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
@@ -120,24 +121,17 @@ void XrayProtocol::stop()
|
||||
{
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto disableKillSwitch = iface->disableKillSwitch();
|
||||
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
||||
qWarning() << "Failed to disable killswitch";
|
||||
if (m_phase != Phase::Active) {
|
||||
return;
|
||||
}
|
||||
m_phase = Phase::Stopping;
|
||||
|
||||
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
||||
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
|
||||
qWarning() << "Failed to start routing ipv6";
|
||||
|
||||
auto restoreResolvers = iface->restoreResolvers();
|
||||
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
|
||||
qWarning() << "Failed to restore resolvers";
|
||||
|
||||
auto deleteTun = iface->deleteTun(tunName);
|
||||
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto deleteTun = iface->deleteTun(m_tunName);
|
||||
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
||||
qWarning() << "Failed to delete tun";
|
||||
|
||||
auto xrayStop = iface->xrayStop();
|
||||
auto xrayStop = iface->xrayStop(m_tunName);
|
||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
||||
qWarning() << "Failed to stop xray";
|
||||
});
|
||||
@@ -162,9 +156,18 @@ void XrayProtocol::stop()
|
||||
m_tun2socksProcess.reset();
|
||||
}
|
||||
|
||||
m_phase = Phase::Inactive;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
void XrayProtocol::setPrimary(const QJsonObject &config)
|
||||
{
|
||||
Q_UNUSED(config)
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
emit primaryReady();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::startTun2Socks()
|
||||
{
|
||||
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
||||
@@ -175,10 +178,23 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
||||
|
||||
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
||||
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl });
|
||||
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(m_tunName), "-proxy", proxyUrl });
|
||||
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::errorOccurred, this,
|
||||
[this](QProcess::ProcessError error) {
|
||||
if (error != QProcess::FailedToStart) {
|
||||
// Other errors are reported via the finished signal or are transient.
|
||||
return;
|
||||
}
|
||||
qCritical() << "Tun2socks failed to start";
|
||||
stop();
|
||||
setLastError(ErrorCode::Tun2SockExecutableMissing);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
|
||||
[this]() {
|
||||
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
||||
if (!readAllStandardError.waitForFinished()) {
|
||||
@@ -217,13 +233,17 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||
if (m_phase == Phase::Active && resourceBusy
|
||||
&& m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||
m_tun2socksRetryCount++;
|
||||
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
|
||||
.arg(m_tun2socksRetryCount)
|
||||
.arg(maxTun2SocksRetries)
|
||||
.arg(tun2socksRetryDelayMs);
|
||||
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
|
||||
if (m_phase != Phase::Active) {
|
||||
return;
|
||||
}
|
||||
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(err);
|
||||
@@ -252,81 +272,17 @@ ErrorCode XrayProtocol::setupRouting()
|
||||
{
|
||||
return IpcClient::withInterface(
|
||||
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
||||
#ifdef Q_OS_WIN
|
||||
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
||||
#endif
|
||||
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||
#ifndef Q_OS_WIN
|
||||
auto createTun = iface->createTun(m_tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
||||
qCritical() << "Failed to assign IP address for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
||||
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
||||
qCritical() << "Failed to set DNS resolvers for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int vpnAdapterIndex = -1;
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (auto &netInterface : netInterfaces) {
|
||||
for (auto &address : netInterface.addressEntries()) {
|
||||
if (m_vpnLocalAddress == address.ip().toString())
|
||||
vpnAdapterIndex = netInterface.index();
|
||||
}
|
||||
}
|
||||
#else
|
||||
static const int vpnAdapterIndex = 0;
|
||||
Q_UNUSED(iface)
|
||||
#endif
|
||||
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
|
||||
if (killSwitchEnabled) {
|
||||
if (vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
||||
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
||||
qCritical() << "Failed to enable killswitch";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
||||
}
|
||||
|
||||
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
|
||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
|
||||
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
||||
|
||||
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
|
||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
||||
qCritical() << "Failed to set routes for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to disable IPv6 routing";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("inetAdapterIndex", inetAdapterIndex);
|
||||
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
||||
config.insert("vpnGateway", m_vpnGateway);
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
||||
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
||||
qCritical() << "Failed to enable peer traffic";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
||||
#endif
|
||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||
return ErrorCode::NoError;
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
|
||||
@@ -20,14 +20,20 @@ public:
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
void setPrimary(const QJsonObject &config) override;
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
Inactive,
|
||||
Active,
|
||||
Stopping,
|
||||
};
|
||||
|
||||
ErrorCode setupRouting();
|
||||
ErrorCode startTun2Socks();
|
||||
|
||||
QJsonObject m_xrayConfig;
|
||||
amnezia::RouteMode m_routeMode;
|
||||
QList<QHostAddress> m_dnsServers;
|
||||
QString m_remoteAddress;
|
||||
|
||||
QString m_socksUser;
|
||||
@@ -38,6 +44,9 @@ private:
|
||||
int m_tun2socksRetryCount = 0;
|
||||
static constexpr int maxTun2SocksRetries = 5;
|
||||
static constexpr int tun2socksRetryDelayMs = 400;
|
||||
|
||||
QString m_tunName;
|
||||
Phase m_phase = Phase::Inactive;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
|
||||
@@ -426,26 +426,6 @@ void SecureAppSettingsRepository::clearSettings()
|
||||
emit settingsCleared();
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::nextAvailableServerName() const
|
||||
{
|
||||
int i = 0;
|
||||
bool nameExist = false;
|
||||
|
||||
do {
|
||||
i++;
|
||||
nameExist = false;
|
||||
QJsonArray servers = QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||
for (const QJsonValue &server : servers) {
|
||||
if (server.toObject().value(configKey::description).toString() == QString("Server") + " " + QString::number(i)) {
|
||||
nameExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (nameExist);
|
||||
|
||||
return QString("Server") + " " + QString::number(i);
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setInstallationUuid(const QString &uuid)
|
||||
{
|
||||
m_settings->setValue("Conf/installationUuid", uuid);
|
||||
|
||||
@@ -90,8 +90,6 @@ public:
|
||||
bool restoreAppConfig(const QByteArray &cfg);
|
||||
void clearSettings();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
@@ -32,6 +33,45 @@ QJsonObject embedStorageServerId(const QString &serverId, const QJsonObject &pay
|
||||
return o;
|
||||
}
|
||||
|
||||
QString storedServerDisplayName(const SecureServersRepository *repository, const QString &serverId)
|
||||
{
|
||||
using Kind = serverConfigUtils::ConfigType;
|
||||
switch (repository->serverKind(serverId)) {
|
||||
case Kind::SelfHostedAdmin:
|
||||
if (const auto cfg = repository->selfHostedAdminConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::SelfHostedUser:
|
||||
if (const auto cfg = repository->selfHostedUserConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Native:
|
||||
if (const auto cfg = repository->nativeConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV2:
|
||||
case Kind::AmneziaFreeV3:
|
||||
case Kind::ExternalPremium:
|
||||
if (const auto cfg = repository->apiV2Config(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::AmneziaPremiumV1:
|
||||
case Kind::AmneziaFreeV2:
|
||||
if (const auto cfg = repository->legacyApiConfig(serverId)) {
|
||||
return cfg->displayName;
|
||||
}
|
||||
break;
|
||||
case Kind::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SecureServersRepository::SecureServersRepository(SecureQSettings *settings, QObject *parent)
|
||||
@@ -153,6 +193,28 @@ void SecureServersRepository::clearServers()
|
||||
syncToStorage();
|
||||
}
|
||||
|
||||
QString SecureServersRepository::nextAvailableServerName() const
|
||||
{
|
||||
QSet<QString> usedNames;
|
||||
usedNames.reserve(m_orderedServerIds.size());
|
||||
|
||||
for (const QString &serverId : m_orderedServerIds) {
|
||||
const QString displayName = storedServerDisplayName(this, serverId);
|
||||
if (!displayName.isEmpty()) {
|
||||
usedNames.insert(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
QString candidate;
|
||||
do {
|
||||
i++;
|
||||
candidate = QStringLiteral("Server %1").arg(i);
|
||||
} while (usedNames.contains(candidate));
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
QString SecureServersRepository::addServer(const QString &serverId, const QJsonObject &serverJson, serverConfigUtils::ConfigType kind)
|
||||
{
|
||||
const QString id = normalizedOrGeneratedServerId(serverId);
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
void clearServers();
|
||||
|
||||
QString nextAvailableServerName() const;
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "tunnel.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "daemon/interfaceconfig.h"
|
||||
|
||||
Tunnel::Tunnel(QString ifname,
|
||||
amnezia::DockerContainer container,
|
||||
QJsonObject config,
|
||||
QString remoteAddress,
|
||||
QObject* parent)
|
||||
: QObject(parent),
|
||||
m_ifname(std::move(ifname)),
|
||||
m_remoteAddress(std::move(remoteAddress)),
|
||||
m_container(container),
|
||||
m_config(std::move(config)) {}
|
||||
|
||||
Tunnel::~Tunnel() = default;
|
||||
|
||||
void Tunnel::prepare() {
|
||||
if (m_state != State::Idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(State::Preparing);
|
||||
|
||||
m_config.insert("ifname", m_ifname);
|
||||
m_protocol.reset(VpnProtocol::factory(m_container, m_config));
|
||||
if (!m_protocol) {
|
||||
setState(State::Failed);
|
||||
emit failed(amnezia::ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_protocol.data(), &VpnProtocol::connectionStateChanged,
|
||||
this, &Tunnel::onProtocolStateChanged);
|
||||
connect(m_protocol.data(), &VpnProtocol::bytesChanged,
|
||||
this, &Tunnel::bytesChanged);
|
||||
connect(m_protocol.data(), &VpnProtocol::tunnelAddressesUpdated,
|
||||
this, &Tunnel::addressesUpdated);
|
||||
connect(m_protocol.data(), &VpnProtocol::primaryReady,
|
||||
this, &Tunnel::onPrimaryReady);
|
||||
connect(m_protocol.data(), &VpnProtocol::primaryFailed,
|
||||
this, &Tunnel::onPrimaryFailed);
|
||||
|
||||
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
|
||||
|
||||
const amnezia::ErrorCode err = m_protocol->start();
|
||||
if (err != amnezia::ErrorCode::NoError) {
|
||||
cancelActivationDeadline();
|
||||
setState(State::Failed);
|
||||
emit failed(err);
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnel::commit() {
|
||||
if (m_state != State::Prepared) {
|
||||
return;
|
||||
}
|
||||
setState(State::Committing);
|
||||
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
|
||||
if (m_protocol) {
|
||||
m_protocol->setPrimary(m_config);
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnel::onPrimaryReady() {
|
||||
if (m_state != State::Committing) {
|
||||
return;
|
||||
}
|
||||
cancelActivationDeadline();
|
||||
setState(State::Active);
|
||||
emit activated();
|
||||
}
|
||||
|
||||
void Tunnel::onPrimaryFailed() {
|
||||
if (m_state != State::Committing) {
|
||||
return;
|
||||
}
|
||||
cancelActivationDeadline();
|
||||
setState(State::Failed);
|
||||
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
|
||||
}
|
||||
|
||||
void Tunnel::deactivate() {
|
||||
if (m_state == State::Gone || m_state == State::Idle) {
|
||||
return;
|
||||
}
|
||||
cancelActivationDeadline();
|
||||
setState(State::Gone);
|
||||
if (m_protocol) {
|
||||
m_protocol->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnel::restart() {
|
||||
deactivate();
|
||||
setState(State::Idle);
|
||||
prepare();
|
||||
}
|
||||
|
||||
void Tunnel::setState(State next) {
|
||||
if (m_state == next) {
|
||||
return;
|
||||
}
|
||||
m_state = next;
|
||||
emit stateChanged(m_state);
|
||||
}
|
||||
|
||||
void Tunnel::startActivationDeadline(int msec) {
|
||||
if (!m_deadline) {
|
||||
m_deadline = new QTimer(this);
|
||||
m_deadline->setSingleShot(true);
|
||||
connect(m_deadline, &QTimer::timeout, this, [this]() {
|
||||
if (m_state != State::Preparing && m_state != State::Committing) {
|
||||
return;
|
||||
}
|
||||
setState(State::Failed);
|
||||
emit failed(amnezia::ErrorCode::InternalError);
|
||||
});
|
||||
}
|
||||
m_deadline->start(msec);
|
||||
}
|
||||
|
||||
void Tunnel::cancelActivationDeadline() {
|
||||
if (m_deadline) {
|
||||
m_deadline->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Tunnel::onProtocolStateChanged(Vpn::ConnectionState state) {
|
||||
if (m_state == State::Preparing && state == Vpn::ConnectionState::Connected) {
|
||||
cancelActivationDeadline();
|
||||
setState(State::Prepared);
|
||||
emit prepared();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool inLiveState = m_state == State::Preparing
|
||||
|| m_state == State::Prepared
|
||||
|| m_state == State::Committing
|
||||
|| m_state == State::Active;
|
||||
const bool isFailureSignal = state == Vpn::ConnectionState::Disconnected
|
||||
|| state == Vpn::ConnectionState::Error;
|
||||
if (inLiveState && isFailureSignal) {
|
||||
cancelActivationDeadline();
|
||||
setState(State::Failed);
|
||||
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef TUNNEL_H
|
||||
#define TUNNEL_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/protocols/vpnProtocol.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
|
||||
class QTimer;
|
||||
|
||||
class Tunnel : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class State {
|
||||
Idle,
|
||||
Preparing,
|
||||
Prepared,
|
||||
Committing,
|
||||
Active,
|
||||
Gone,
|
||||
Failed,
|
||||
};
|
||||
Q_ENUM(State)
|
||||
|
||||
Tunnel(QString ifname,
|
||||
amnezia::DockerContainer container,
|
||||
QJsonObject config,
|
||||
QString remoteAddress,
|
||||
QObject* parent = nullptr);
|
||||
~Tunnel() override;
|
||||
|
||||
const QString& ifname() const { return m_ifname; }
|
||||
const QString& remoteAddress() const { return m_remoteAddress; }
|
||||
amnezia::DockerContainer container() const { return m_container; }
|
||||
const QJsonObject& config() const { return m_config; }
|
||||
State state() const { return m_state; }
|
||||
QSharedPointer<VpnProtocol> protocol() const { return m_protocol; }
|
||||
|
||||
const QString& handoverIfname() const { return m_handoverIfname; }
|
||||
void setHandoverIfname(const QString& ifname) { m_handoverIfname = ifname; }
|
||||
void clearHandoverIfname() { m_handoverIfname.clear(); }
|
||||
|
||||
virtual void prepare();
|
||||
virtual void commit();
|
||||
virtual void deactivate();
|
||||
virtual void restart();
|
||||
|
||||
signals:
|
||||
void prepared();
|
||||
void activated();
|
||||
void failed(amnezia::ErrorCode);
|
||||
void stateChanged(Tunnel::State);
|
||||
void bytesChanged(quint64 rxBytes, quint64 txBytes);
|
||||
void addressesUpdated(const QString& gateway, const QString& localAddress);
|
||||
|
||||
protected:
|
||||
void setState(State);
|
||||
void startActivationDeadline(int msec);
|
||||
void cancelActivationDeadline();
|
||||
|
||||
QString m_ifname;
|
||||
QString m_remoteAddress;
|
||||
QString m_handoverIfname;
|
||||
amnezia::DockerContainer m_container;
|
||||
QJsonObject m_config;
|
||||
QSharedPointer<VpnProtocol> m_protocol;
|
||||
|
||||
private:
|
||||
void onProtocolStateChanged(Vpn::ConnectionState state);
|
||||
void onPrimaryReady();
|
||||
void onPrimaryFailed();
|
||||
|
||||
State m_state = State::Idle;
|
||||
QTimer* m_deadline = nullptr;
|
||||
};
|
||||
|
||||
#endif // TUNNEL_H
|
||||
@@ -84,15 +84,14 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
const int httpStatusCodeNotFound = 404;
|
||||
const int httpStatusCodeNotImplemented = 501;
|
||||
const int httpStatusCodePaymentRequired = 402;
|
||||
const int httpStatusCodeTooManyRequests = 429;
|
||||
const int httpStatusCodeRequestTimeout = 408;
|
||||
const int httpStatusCodeUnprocessableEntity = 422;
|
||||
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return amnezia::ErrorCode::ApiConfigSslError;
|
||||
}
|
||||
if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
}
|
||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << replyError;
|
||||
@@ -107,6 +106,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
const int httpStatusFromBody = jsonObj.value(QStringLiteral("http_status")).toInt(-1);
|
||||
|
||||
if (httpStatusFromBody == httpStatusCodeTooManyRequests) {
|
||||
return amnezia::ErrorCode::ApiRateLimitError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeConflict) {
|
||||
if (apiErrorMessageFromJson(jsonObj).contains(trialAlreadyUsedMessage, Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiTrialAlreadyUsedError;
|
||||
@@ -116,6 +119,9 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
if (httpStatusFromBody == httpStatusCodeNotFound) {
|
||||
return amnezia::ErrorCode::ApiNotFoundError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeRequestTimeout) {
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
@@ -126,9 +132,28 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
if (httpStatusFromBody == httpStatusCodePaymentRequired) {
|
||||
const QString message = apiErrorMessageFromJson(jsonObj);
|
||||
if (message.contains(QLatin1String("refresh_captcha"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaRefreshError;
|
||||
}
|
||||
if (message.contains(QLatin1String("invalid_captcha"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaInvalidError;
|
||||
}
|
||||
if (jsonObj.contains(QStringLiteral("captcha_id")) || jsonObj.contains(QStringLiteral("captcha_image"))
|
||||
|| message.compare(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive) == 0
|
||||
|| message.contains(QLatin1String("rate_limit_exceeded"), Qt::CaseInsensitive)) {
|
||||
return amnezia::ErrorCode::ApiCaptchaRequiredError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiSubscriptionNotActiveError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
|
||||
if (httpStatusFromBody >= 300) {
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
}
|
||||
|
||||
qDebug() << "something went wrong";
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
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("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace amnezia
|
||||
ServerCgroupMountpoint = 212,
|
||||
DockerPullRateLimit = 213,
|
||||
ServerLinuxKernelTooOld = 214,
|
||||
XrayServerConfigInvalid = 215,
|
||||
XrayServerNoVlessClients = 216,
|
||||
XrayRealityKeysReadFailed = 217,
|
||||
ServerContainerRuntimeNotSupported = 218,
|
||||
ContainerRuntimeServiceNotRunning = 219,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -76,6 +81,7 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
@@ -98,6 +104,10 @@ namespace amnezia
|
||||
ApiSubscriptionNotActiveError = 1114,
|
||||
ApiNoPurchasedSubscriptionsError = 1115,
|
||||
ApiTrialAlreadyUsedError = 1116,
|
||||
ApiCaptchaRequiredError = 1117,
|
||||
ApiCaptchaInvalidError = 1118,
|
||||
ApiCaptchaRefreshError = 1119,
|
||||
ApiRateLimitError = 1120,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
@@ -116,5 +126,3 @@ namespace amnezia
|
||||
Q_DECLARE_METATYPE(amnezia::ErrorCode)
|
||||
|
||||
#endif // ERRORCODES_H
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,17 @@ QString errorString(ErrorCode code) {
|
||||
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
||||
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
|
||||
case(ErrorCode::XrayServerConfigInvalid):
|
||||
errorMessage = QObject::tr("Server error: invalid or unreadable XRay server configuration");
|
||||
break;
|
||||
case(ErrorCode::XrayServerNoVlessClients):
|
||||
errorMessage = QObject::tr("Server error: XRay server has no VLESS clients");
|
||||
break;
|
||||
case(ErrorCode::XrayRealityKeysReadFailed):
|
||||
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
|
||||
break;
|
||||
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
|
||||
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
@@ -60,6 +71,7 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
@@ -84,6 +96,10 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break;
|
||||
case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break;
|
||||
case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email address has already been used to activate a trial"); break;
|
||||
case (ErrorCode::ApiCaptchaRequiredError): errorMessage = QObject::tr("CAPTCHA verification is required"); break;
|
||||
case (ErrorCode::ApiCaptchaInvalidError): errorMessage = QObject::tr("CAPTCHA was incorrect. Please try again"); break;
|
||||
case (ErrorCode::ApiCaptchaRefreshError): errorMessage = QObject::tr("CAPTCHA refreshed. Please try again"); break;
|
||||
case (ErrorCode::ApiRateLimitError): errorMessage = QObject::tr("Too many requests. Please try again later"); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 100;
|
||||
constexpr int BUFFER_SIZE = 8192;
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char msgbuf[100], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
if((NLMSG_OK(nlh, received_bytes) == 0) ||
|
||||
(nlh->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return {};
|
||||
@@ -355,13 +355,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
int remaining = msg_len + received_bytes;
|
||||
nlh = (struct nlmsghdr *) buffer;
|
||||
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
@@ -370,6 +372,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
/* Reset per-route to avoid cross-route state pollution */
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
@@ -391,10 +397,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
|
||||
if ((*gateway_address) && (*interface)) {
|
||||
if (QString::fromUtf8(interface).startsWith("amn")) {
|
||||
continue;
|
||||
}
|
||||
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(*gateway_address) || !(*interface))
|
||||
qDebug() << "getGatewayAndIface: no gateway found";
|
||||
close(sock);
|
||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||
#endif
|
||||
@@ -449,10 +460,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||
{
|
||||
char ifname[IF_NAMESIZE] = {0};
|
||||
const bool isTun = if_indextoname(rt->rtm_index, ifname)
|
||||
&& QString::fromUtf8(ifname).startsWith("utun");
|
||||
|
||||
if (afinet_type[ip_type] == AF_INET)
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
if (isTun) continue;
|
||||
char dstStr4[INET_ADDRSTRLEN];
|
||||
char srcStr4[INET_ADDRSTRLEN];
|
||||
memcpy(srcStr4,
|
||||
@@ -470,6 +486,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
if (isTun) continue;
|
||||
char dstStr6[INET6_ADDRSTRLEN];
|
||||
char srcStr6[INET6_ADDRSTRLEN];
|
||||
memcpy(srcStr6,
|
||||
|
||||
@@ -295,6 +295,8 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf
|
||||
|
||||
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
|
||||
vars.append({{"$MTPROXY_SECRET", c.secret}});
|
||||
vars.append({{"$MTPROXY_REGENERATE_SECRET",
|
||||
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0")}});
|
||||
vars.append({{"$MTPROXY_TAG", c.tag}});
|
||||
vars.append({{"$MTPROXY_TRANSPORT_MODE",
|
||||
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
|
||||
@@ -350,6 +352,8 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
|
||||
vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } });
|
||||
vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } });
|
||||
vars.append({ { "$TELEMT_SECRET", c.secret } });
|
||||
vars.append({ { "$TELEMT_REGENERATE_SECRET",
|
||||
c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0") } });
|
||||
vars.append({ { "$TELEMT_TAG", c.tag } });
|
||||
QString tlsDomain = c.tlsDomain;
|
||||
if (tlsDomain.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
#include "vpnTrafficGuard.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QEventLoop>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QPointer>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/utils/ipcClient.h"
|
||||
#endif
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/tunnel.h"
|
||||
#include "mozilla/localsocketcontroller.h"
|
||||
|
||||
VpnTrafficGuard::VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject *parent)
|
||||
: QObject(parent), m_appSettingsRepository(appSettings)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
VpnTrafficGuard::~VpnTrafficGuard()
|
||||
{
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::setConfig(const QJsonObject &config)
|
||||
{
|
||||
m_config = config;
|
||||
}
|
||||
|
||||
bool VpnTrafficGuard::allowEndpoint(const QString &remoteAddress, const QString &ifname)
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (remoteAddress.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (m_allowedEndpoints.contains(remoteAddress)) {
|
||||
return true;
|
||||
}
|
||||
m_allowedEndpoints.append(remoteAddress);
|
||||
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(ifname, QStringList(remoteAddress));
|
||||
return reply.waitForFinished(1000) && reply.returnValue();
|
||||
});
|
||||
#else
|
||||
Q_UNUSED(remoteAddress)
|
||||
Q_UNUSED(ifname)
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::setupRoutes(const QJsonObject &vpnConfiguration, const QSharedPointer<VpnProtocol> &protocol, const QString &remoteAddress)
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (!m_appSettingsRepository) {
|
||||
qCritical() << "VpnTrafficGuard::setupRoutes: repositories not initialized";
|
||||
return;
|
||||
}
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->resetIpStack();
|
||||
auto flushDns = iface->flushDns();
|
||||
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||
qDebug() << "VpnTrafficGuard::setupRoutes: Successfully flushed DNS";
|
||||
else
|
||||
qWarning() << "VpnTrafficGuard::setupRoutes: Failed to flush DNS";
|
||||
|
||||
const QString proto = vpnConfiguration.value(configKey::vpnProto).toString();
|
||||
const bool isWgBased = (proto == ProtocolUtils::protoToString(Proto::Awg) ||
|
||||
proto == ProtocolUtils::protoToString(Proto::WireGuard));
|
||||
if (!isWgBased) {
|
||||
if (!protocol) {
|
||||
qWarning() << "VpnTrafficGuard::setupRoutes: protocol is null";
|
||||
return;
|
||||
}
|
||||
QString dns1 = vpnConfiguration.value(configKey::dns1).toString();
|
||||
QString dns2 = vpnConfiguration.value(configKey::dns2).toString();
|
||||
const QString xrayIfname = vpnConfiguration.value("ifname").toString();
|
||||
#ifdef Q_OS_MACOS
|
||||
if (!m_appSettingsRepository->isSitesSplitTunnelingEnabled() || m_appSettingsRepository->routeMode() != amnezia::RouteMode::VpnAllExceptSites) {
|
||||
iface->routeAddListVia(xrayIfname, protocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||
}
|
||||
#else
|
||||
iface->routeAddListVia(xrayIfname, protocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||
#endif
|
||||
|
||||
if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) {
|
||||
iface->routeDeleteList(protocol->vpnGateway(), QStringList() << "0.0.0.0");
|
||||
if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnOnlyForwardSites) {
|
||||
QPointer<VpnProtocol> protocolPtr(protocol.data());
|
||||
QTimer::singleShot(1000, protocol.data(),
|
||||
[this, protocolPtr]() {
|
||||
if (!protocolPtr) {
|
||||
return;
|
||||
}
|
||||
addSplitTunnelRoutes(protocolPtr->vpnGateway(), m_appSettingsRepository->routeMode());
|
||||
});
|
||||
} else if (m_appSettingsRepository->routeMode() == amnezia::route_mode_ns::VpnAllExceptSites) {
|
||||
iface->routeAddListVia(xrayIfname, protocol->vpnGateway(), QStringList() << "0.0.0.0/1" << "128.0.0.0/1");
|
||||
|
||||
iface->routeAddList(protocol->routeGateway(), QStringList() << remoteAddress);
|
||||
#ifdef Q_OS_MACOS
|
||||
iface->routeAddList(protocol->routeGateway(), QStringList() << dns1 << dns2);
|
||||
#endif
|
||||
addSplitTunnelRoutes(protocol->routeGateway(), m_appSettingsRepository->routeMode());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::addSplitTunnelRoutes(const QString &gw, amnezia::RouteMode mode)
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (!m_appSettingsRepository) {
|
||||
qCritical() << "VpnTrafficGuard::addSplitTunnelRoutes: repositories not initialized";
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList ips;
|
||||
QStringList sites;
|
||||
const QVariantMap &m = m_appSettingsRepository->vpnSites(mode);
|
||||
for (auto i = m.constBegin(); i != m.constEnd(); ++i) {
|
||||
if (NetworkUtilities::checkIpSubnetFormat(i.key())) {
|
||||
ips.append(i.key());
|
||||
} else {
|
||||
if (NetworkUtilities::checkIpSubnetFormat(i.value().toString())) {
|
||||
ips.append(i.value().toString());
|
||||
}
|
||||
sites.append(i.key());
|
||||
}
|
||||
}
|
||||
ips.removeDuplicates();
|
||||
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->routeAddList(gw, ips);
|
||||
});
|
||||
|
||||
for (const QString &site : sites) {
|
||||
const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) {
|
||||
for (const QHostAddress &addr : hostInfo.addresses()) {
|
||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||
const QString &ip = addr.toString();
|
||||
if (!ips.contains(ip)) {
|
||||
IpcClient::withInterface([gw, ip](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->routeAddList(gw, QStringList() << ip);
|
||||
});
|
||||
m_appSettingsRepository->addVpnSite(mode, site, ip);
|
||||
}
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto reply = iface->flushDns();
|
||||
if (!reply.waitForFinished() || !reply.returnValue())
|
||||
qWarning() << "VpnTrafficGuard::addSplitTunnelRoutes: Failed to flush DNS";
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
QHostInfo::lookupHost(site, this, cbResolv);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::finishFirewallHandover(Tunnel* tunnel)
|
||||
{
|
||||
#if defined(AMNEZIA_DESKTOP) && defined(Q_OS_WIN)
|
||||
if (!tunnel) return;
|
||||
const QString handoverIfname = tunnel->handoverIfname();
|
||||
if (handoverIfname.isEmpty() || handoverIfname == tunnel->ifname()) {
|
||||
tunnel->clearHandoverIfname();
|
||||
return;
|
||||
}
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->disableKillSwitchForTunnel(handoverIfname, QStringList());
|
||||
});
|
||||
tunnel->clearHandoverIfname();
|
||||
#else
|
||||
Q_UNUSED(tunnel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::applyKillSwitch(Tunnel* tunnel, const QString &gateway, const QString &localAddress)
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
finishFirewallHandover(tunnel);
|
||||
|
||||
QJsonObject updatedConfig = m_config;
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
#ifdef Q_OS_WIN
|
||||
const QString ifname = updatedConfig.value("ifname").toString();
|
||||
if (!ifname.isEmpty()) {
|
||||
updatedConfig.insert("vpnGateway", gateway);
|
||||
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
|
||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
iface->enableKillSwitch(updatedConfig, 0);
|
||||
}
|
||||
iface->enablePeerTraffic(updatedConfig);
|
||||
} else {
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
||||
{
|
||||
if (localAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||
updatedConfig.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
||||
updatedConfig.insert("vpnGateway", gateway);
|
||||
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
|
||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
iface->enableKillSwitch(updatedConfig, netInterfaces.at(i).index());
|
||||
}
|
||||
iface->enablePeerTraffic(updatedConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
updatedConfig.insert("vpnServer",
|
||||
NetworkUtilities::getIPAddress(updatedConfig.value(amnezia::configKey::hostName).toString()));
|
||||
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(updatedConfig, 0);
|
||||
//TODO: why it takes so long?
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::applyKillSwitch: Failed to enable killswitch";
|
||||
} else {
|
||||
qDebug() << "VpnTrafficGuard::applyKillSwitch: Successfully enabled killswitch";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const QString proto = updatedConfig.value(configKey::vpnProto).toString();
|
||||
const bool isXrayBased = (proto == ProtocolUtils::protoToString(Proto::Xray) ||
|
||||
proto == ProtocolUtils::protoToString(Proto::SSXray));
|
||||
if (isXrayBased) {
|
||||
if (updatedConfig.value(configKey::splitTunnelType).toInt() == amnezia::route_mode_ns::VpnAllSites) {
|
||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
||||
const QString xrayIfname = tunnel ? tunnel->ifname() : QString();
|
||||
auto routeAddList = iface->routeAddListVia(xrayIfname, gateway, subnets);
|
||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
||||
qCritical() << "Failed to set routes for TUN";
|
||||
}
|
||||
}
|
||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to disable IPv6 routing";
|
||||
} else {
|
||||
m_ipv6RoutingStopped = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::flushAll()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->restoreTunnelResolvers();
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
m_allowedEndpoints.clear();
|
||||
//TODO: why it takes so long?
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::flushAll: Failed to disable killswitch";
|
||||
} else {
|
||||
qDebug() << "VpnTrafficGuard::flushAll: Successfully disabled killswitch";
|
||||
}
|
||||
auto flushDns = iface->flushDns();
|
||||
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||
qDebug() << "VpnTrafficGuard::flushAll: Successfully flushed DNS";
|
||||
else
|
||||
qWarning() << "VpnTrafficGuard::flushAll: Failed to flush DNS";
|
||||
|
||||
auto clearSavedRoutes = iface->clearSavedRoutes();
|
||||
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
|
||||
qDebug() << "VpnTrafficGuard::flushAll: Successfully cleared saved routes";
|
||||
else
|
||||
qWarning() << "VpnTrafficGuard::flushAll: Failed to clear saved routes";
|
||||
if (m_ipv6RoutingStopped) {
|
||||
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
||||
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to enable IPv6 routing";
|
||||
} else {
|
||||
m_ipv6RoutingStopped = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
QStringList allowedIpPrefixesFor(const QJsonObject& activateJson)
|
||||
{
|
||||
QStringList prefixes;
|
||||
const QJsonArray ranges = activateJson.value("allowedIPAddressRanges").toArray();
|
||||
for (const QJsonValue& v : ranges) {
|
||||
const QJsonObject r = v.toObject();
|
||||
const QString addr = r.value("address").toString();
|
||||
if (addr.isEmpty()) continue;
|
||||
prefixes.append(QStringLiteral("%1/%2").arg(addr).arg(r.value("range").toInt()));
|
||||
}
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
QStringList excludedAddressesFor(const QJsonObject& activateJson)
|
||||
{
|
||||
QStringList addrs;
|
||||
const QJsonArray excluded = activateJson.value("excludedAddresses").toArray();
|
||||
for (const QJsonValue& v : excluded) {
|
||||
const QString s = v.toString();
|
||||
if (!s.isEmpty()) addrs.append(s);
|
||||
}
|
||||
return addrs;
|
||||
}
|
||||
|
||||
QStringList resolversFor(const QJsonObject& activateJson)
|
||||
{
|
||||
QStringList dns;
|
||||
const QString primary = activateJson.value("primaryDnsServer").toString();
|
||||
if (!primary.isEmpty()) dns.append(primary);
|
||||
const QString secondary = activateJson.value("secondaryDnsServer").toString();
|
||||
if (!secondary.isEmpty()) dns.append(secondary);
|
||||
return dns;
|
||||
}
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::reserve(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
allowEndpoint(tunnel->remoteAddress(), tunnel->ifname());
|
||||
#else
|
||||
Q_UNUSED(tunnel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::release(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
disconnect(tunnel, nullptr, this, nullptr);
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
m_allowedEndpoints.removeAll(tunnel->remoteAddress());
|
||||
IpcClient::withInterface([this, &tunnel](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->disableKillSwitchForTunnel(tunnel->ifname(), m_allowedEndpoints);
|
||||
});
|
||||
#else
|
||||
Q_UNUSED(tunnel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::applyPolicy(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
const QString ifname = tunnel->ifname();
|
||||
|
||||
if (VpnProtocol::isXrayBased(tunnel->container())) {
|
||||
const QJsonObject cfg = tunnel->config();
|
||||
const QString primary = cfg.value(amnezia::configKey::dns1).toString();
|
||||
const QString secondary = cfg.value(amnezia::configKey::dns2).toString();
|
||||
QList<QHostAddress> dns;
|
||||
if (!primary.isEmpty()) dns.append(QHostAddress(primary));
|
||||
if (!secondary.isEmpty() && secondary != primary) dns.append(QHostAddress(secondary));
|
||||
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto updateRes = iface->updateResolvers(ifname, dns);
|
||||
if (!updateRes.waitForFinished() || !updateRes.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::applyPolicy: updateResolvers failed for" << ifname;
|
||||
}
|
||||
#ifdef Q_OS_MAC
|
||||
const auto gw = NetworkUtilities::getGatewayAndIface();
|
||||
const QString uplinkIface = gw.second.name();
|
||||
const QString uplinkGateway = gw.first;
|
||||
if (!uplinkIface.isEmpty() && !uplinkGateway.isEmpty()) {
|
||||
auto add = iface->xrayAddUplinkRoutes(uplinkIface, uplinkGateway);
|
||||
if (!add.waitForFinished() || !add.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::applyPolicy: xrayAddUplinkRoutes failed on" << uplinkIface;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname());
|
||||
const QStringList prefixes = allowedIpPrefixesFor(activate);
|
||||
const QStringList excluded = excludedAddressesFor(activate);
|
||||
const QStringList dns = resolversFor(activate);
|
||||
const QString peer = tunnel->remoteAddress();
|
||||
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
if (!peer.isEmpty()) iface->addExclusionRoute(ifname, peer);
|
||||
for (const QString& addr : excluded) {
|
||||
iface->addExclusionRoute(ifname, addr);
|
||||
}
|
||||
for (const QString& prefix : prefixes) {
|
||||
iface->addAllowedIp(ifname, prefix);
|
||||
}
|
||||
iface->setTunnelResolvers(ifname, dns);
|
||||
iface->flushDns();
|
||||
});
|
||||
#else
|
||||
Q_UNUSED(tunnel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::revokePolicy(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
const QString ifname = tunnel->ifname();
|
||||
|
||||
if (VpnProtocol::isXrayBased(tunnel->container())) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->restoreResolvers();
|
||||
#ifdef Q_OS_MAC
|
||||
const auto gw = NetworkUtilities::getGatewayAndIface();
|
||||
const QString uplinkIface = gw.second.name();
|
||||
const QString uplinkGateway = gw.first;
|
||||
if (!uplinkIface.isEmpty()) {
|
||||
iface->xrayRemoveUplinkRoutes(uplinkIface, uplinkGateway);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname());
|
||||
const QStringList prefixes = allowedIpPrefixesFor(activate);
|
||||
const QStringList excluded = excludedAddressesFor(activate);
|
||||
const QString peer = tunnel->remoteAddress();
|
||||
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
for (const QString& prefix : prefixes) {
|
||||
iface->delAllowedIp(ifname, prefix);
|
||||
}
|
||||
for (const QString& addr : excluded) {
|
||||
iface->delExclusionRoute(ifname, addr);
|
||||
}
|
||||
if (!peer.isEmpty()) iface->delExclusionRoute(ifname, peer);
|
||||
});
|
||||
#else
|
||||
Q_UNUSED(tunnel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::bringUp(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
reserve(tunnel);
|
||||
tunnel->prepare();
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::commit(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QString ipv4 = tunnel->config().value(QStringLiteral("deviceIpv4Address")).toString();
|
||||
const QString ipv6 = tunnel->config().value(QStringLiteral("deviceIpv6Address")).toString();
|
||||
if (!ipv4.isEmpty() || !ipv6.isEmpty()) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto ap = iface->applyAdapterAddress(tunnel->ifname(), ipv4, ipv6);
|
||||
if (!ap.waitForFinished(15000) || !ap.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::commit: applyAdapterAddress failed for"
|
||||
<< tunnel->ifname();
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
applyPolicy(tunnel);
|
||||
connect(tunnel, &Tunnel::activated, this, [this, tunnel] {
|
||||
if (auto p = tunnel->protocol()) {
|
||||
applyKillSwitch(tunnel, p->vpnGateway(), p->vpnLocalAddress());
|
||||
}
|
||||
});
|
||||
#ifdef Q_OS_WIN
|
||||
connect(tunnel, &Tunnel::addressesUpdated, this,
|
||||
[this, tunnel](const QString& gw, const QString& la) {
|
||||
applyKillSwitch(tunnel, gw, la);
|
||||
});
|
||||
#endif
|
||||
tunnel->commit();
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::tearDown(Tunnel* tunnel)
|
||||
{
|
||||
if (!tunnel) return;
|
||||
revokePolicy(tunnel);
|
||||
release(tunnel);
|
||||
tunnel->deactivate();
|
||||
}
|
||||
|
||||
void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to)
|
||||
{
|
||||
if (!to) return;
|
||||
if (from) {
|
||||
to->setHandoverIfname(from->ifname());
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (from) {
|
||||
const QString fromIpv4 = from->config().value(QStringLiteral("deviceIpv4Address")).toString();
|
||||
const QString fromIpv6 = from->config().value(QStringLiteral("deviceIpv6Address")).toString();
|
||||
if (!fromIpv4.isEmpty() || !fromIpv6.isEmpty()) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto rm = iface->removeAdapterAddress(from->ifname(), fromIpv4, fromIpv6);
|
||||
if (!rm.waitForFinished(2000) || !rm.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::swap: removeAdapterAddress failed for"
|
||||
<< from->ifname();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer guard;
|
||||
guard.setSingleShot(true);
|
||||
const auto activatedConn = connect(to, &Tunnel::activated, &loop, &QEventLoop::quit);
|
||||
const auto failedConn = connect(to, &Tunnel::failed, &loop, [&loop](amnezia::ErrorCode) { loop.quit(); });
|
||||
const auto timeoutConn = connect(&guard, &QTimer::timeout, &loop, [&loop]() {
|
||||
qWarning() << "VpnTrafficGuard::swap: timed out waiting for new tunnel activation";
|
||||
loop.quit();
|
||||
});
|
||||
|
||||
commit(to);
|
||||
|
||||
guard.start(5000);
|
||||
loop.exec();
|
||||
guard.stop();
|
||||
|
||||
disconnect(activatedConn);
|
||||
disconnect(failedConn);
|
||||
disconnect(timeoutConn);
|
||||
|
||||
// Service IPCs are processed in order on a single connection. Wait on a trailing
|
||||
// sync request so the killswitch enable from the activation handlers is fully
|
||||
// applied before we deactivate the previous tunnel.
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto reply = iface->flushDns();
|
||||
if (!reply.waitForFinished(5000) || !reply.returnValue()) {
|
||||
qWarning() << "VpnTrafficGuard::swap: trailing sync IPC timed out or failed";
|
||||
}
|
||||
});
|
||||
|
||||
if (from) {
|
||||
m_allowedEndpoints.removeAll(from->remoteAddress());
|
||||
#ifndef Q_OS_WIN
|
||||
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
iface->resetKillSwitchAllowedRange(m_allowedEndpoints);
|
||||
});
|
||||
#endif
|
||||
revokePolicy(from);
|
||||
from->deactivate();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
if (VpnProtocol::isXrayBased(to->container())) {
|
||||
if (auto p = to->protocol()) {
|
||||
applyKillSwitch(to, p->vpnGateway(), p->vpnLocalAddress());
|
||||
setupRoutes(to->config(), p, to->remoteAddress());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef VPNTRAFFICGUARD_H
|
||||
#define VPNTRAFFICGUARD_H
|
||||
|
||||
#include <qobject.h>
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "protocols/vpnProtocol.h"
|
||||
|
||||
class Tunnel;
|
||||
|
||||
class VpnTrafficGuard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject* parent = nullptr);
|
||||
~VpnTrafficGuard() override;
|
||||
void setConfig(const QJsonObject &config);
|
||||
void setupRoutes(const QJsonObject &vpnConfiguration,
|
||||
const QSharedPointer<VpnProtocol> &protocol,
|
||||
const QString &remoteAddress);
|
||||
|
||||
void flushAll();
|
||||
bool allowEndpoint(const QString &remoteAddress, const QString &ifname = QString());
|
||||
void applyKillSwitch(Tunnel* tunnel, const QString &vpnGateway, const QString &vpnLocalAddress);
|
||||
|
||||
void reserve(Tunnel* tunnel);
|
||||
void release(Tunnel* tunnel);
|
||||
void applyPolicy(Tunnel* tunnel);
|
||||
void revokePolicy(Tunnel* tunnel);
|
||||
|
||||
void bringUp(Tunnel* tunnel);
|
||||
void commit(Tunnel* tunnel);
|
||||
void tearDown(Tunnel* tunnel);
|
||||
void swap(Tunnel* from, Tunnel* to);
|
||||
|
||||
private:
|
||||
void addSplitTunnelRoutes(const QString &gateway, amnezia::RouteMode mode);
|
||||
void finishFirewallHandover(Tunnel* tunnel);
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
QJsonObject m_config;
|
||||
bool m_ipv6RoutingStopped = false;
|
||||
QStringList m_allowedEndpoints;
|
||||
};
|
||||
|
||||
#endif // VPNTRAFFICGUARD_H
|
||||
+178
-216
@@ -34,8 +34,8 @@ Daemon::Daemon(QObject* parent) : QObject(parent) {
|
||||
Q_ASSERT(s_daemon == nullptr);
|
||||
s_daemon = this;
|
||||
|
||||
m_handshakeTimer.setSingleShot(true);
|
||||
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake);
|
||||
m_activationTimer.setSingleShot(false);
|
||||
connect(&m_activationTimer, &QTimer::timeout, this, &Daemon::checkActivations);
|
||||
}
|
||||
|
||||
Daemon::~Daemon() {
|
||||
@@ -43,6 +43,9 @@ Daemon::~Daemon() {
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
qDeleteAll(m_tunnels);
|
||||
m_tunnels.clear();
|
||||
|
||||
Q_ASSERT(s_daemon == this);
|
||||
s_daemon = nullptr;
|
||||
}
|
||||
@@ -53,69 +56,38 @@ Daemon* Daemon::instance() {
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
bool Daemon::activate(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
|
||||
logger.debug() << "Activating tunnel";
|
||||
|
||||
// There are 3 possible scenarios in which this method is called:
|
||||
//
|
||||
// 1. the VPN is off: the method tries to enable the VPN.
|
||||
// 2. the VPN is on and the platform doesn't support the server-switching:
|
||||
// this method calls deactivate() and then it continues as 1.
|
||||
// 3. the VPN is on and the platform supports the server-switching: this
|
||||
// method calls switchServer().
|
||||
//
|
||||
// At the end, if the activation succeds, the `connected` signal is emitted.
|
||||
// If the activation abort's for any reason `the `activationFailure` signal is
|
||||
// emitted.
|
||||
logger.debug() << "Activating interface";
|
||||
auto emit_failure_guard = qScopeGuard([this] { emit activationFailure(); });
|
||||
|
||||
if (m_connections.contains(config.m_hopType)) {
|
||||
if (supportServerSwitching(config)) {
|
||||
logger.debug() << "Already connected. Server switching supported.";
|
||||
|
||||
if (!switchServer(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = run(Switch, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||
if (!wg) {
|
||||
wg = createWgUtils();
|
||||
if (!wg) {
|
||||
logger.error() << "Failed to create wireguard utils.";
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Already connected. Server switching not supported.";
|
||||
if (!deactivate(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||
if (activate(config)) {
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
m_tunnels.insert(ifname, wg);
|
||||
}
|
||||
if (m_primaryIfname.isEmpty()) {
|
||||
m_primaryIfname = ifname;
|
||||
}
|
||||
|
||||
ConnectionState& cs = m_connections[ifname];
|
||||
cs.m_config = config;
|
||||
cs.m_date = QDateTime();
|
||||
cs.m_deadline = QDateTime::currentDateTime().addMSecs(ACTIVATION_TIMEOUT_MSEC);
|
||||
|
||||
auto failure_guard = qScopeGuard([this, ifname] {
|
||||
deactivateTunnel(ifname);
|
||||
});
|
||||
|
||||
prepareActivation(config);
|
||||
|
||||
// Bring up the wireguard interface if not already done.
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
// Create the interface.
|
||||
if (!wgutils()->addInterface(config)) {
|
||||
if (!wg->interfaceExists()) {
|
||||
InterfaceConfig bringupConfig = config;
|
||||
bringupConfig.m_deferAddressSetup = (m_primaryIfname != ifname);
|
||||
if (!wg->addInterface(bringupConfig)) {
|
||||
logger.error() << "Interface creation failed.";
|
||||
return false;
|
||||
}
|
||||
@@ -131,60 +103,71 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure routing for excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Add the peer to this interface.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
if (!wg->updatePeer(config)) {
|
||||
logger.error() << "Peer creation failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
if (!m_activationTimer.isActive()) {
|
||||
m_activationTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
}
|
||||
|
||||
failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::setPrimary(const QString& ifname, const InterfaceConfig& config) {
|
||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||
if (!wg) {
|
||||
logger.error() << "setPrimary: no tunnel for" << ifname;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "setPrimary" << wg->interfaceName();
|
||||
|
||||
const QString priorPrimary = m_primaryIfname;
|
||||
m_primaryIfname = ifname;
|
||||
|
||||
auto failure_guard = qScopeGuard([this, ifname, priorPrimary] {
|
||||
deactivateTunnel(ifname);
|
||||
m_primaryIfname = priorPrimary;
|
||||
});
|
||||
|
||||
if (!run(Up, config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_connections[ifname].m_config = config;
|
||||
|
||||
bool status = run(Up, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_primaryDnsServer));
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
|
||||
}
|
||||
bool Daemon::deactivateTunnel(const QString& ifname) {
|
||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||
const ConnectionState cs = m_connections.value(ifname);
|
||||
const InterfaceConfig& config = cs.m_config;
|
||||
const bool wasPrimary = (ifname == m_primaryIfname);
|
||||
const bool isLastTunnel = wg && m_tunnels.size() == 1;
|
||||
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||
}
|
||||
|
||||
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
|
||||
return false;
|
||||
if (wg) {
|
||||
logger.debug() << "deactivateTunnel" << wg->interfaceName();
|
||||
if (isLastTunnel) {
|
||||
for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
|
||||
wg->deleteExclusionRoute(prefix);
|
||||
}
|
||||
m_excludedAddrSet.clear();
|
||||
}
|
||||
wg->deletePeer(config);
|
||||
wg->deleteInterface();
|
||||
m_tunnels.remove(ifname);
|
||||
delete wg;
|
||||
}
|
||||
|
||||
m_connections.remove(ifname);
|
||||
if (wasPrimary) {
|
||||
m_primaryIfname.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -209,26 +192,60 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
||||
bool Daemon::addExclusionRoute(const QString &ifname, const QString &addr) {
|
||||
IPAddress prefix(addr);
|
||||
if (m_excludedAddrSet.contains(prefix)) {
|
||||
m_excludedAddrSet[prefix]++;
|
||||
return true;
|
||||
}
|
||||
if (!wgutils()->addExclusionRoute(prefix)) {
|
||||
WireguardUtils* wg = wgutilsFor(ifname);
|
||||
if (!wg) wg = primaryWgutils();
|
||||
if (!wg || !wg->addExclusionRoute(prefix)) {
|
||||
return false;
|
||||
}
|
||||
m_excludedAddrSet[prefix] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
||||
Q_ASSERT(m_excludedAddrSet.contains(prefix));
|
||||
bool Daemon::delExclusionRoute(const QString &ifname, const QString &addr) {
|
||||
IPAddress prefix(addr);
|
||||
if (!m_excludedAddrSet.contains(prefix)) {
|
||||
return false;
|
||||
}
|
||||
if (m_excludedAddrSet[prefix] > 1) {
|
||||
m_excludedAddrSet[prefix]--;
|
||||
return true;
|
||||
}
|
||||
m_excludedAddrSet.remove(prefix);
|
||||
return wgutils()->deleteExclusionRoute(prefix);
|
||||
WireguardUtils* wg = wgutilsFor(ifname);
|
||||
if (!wg) wg = primaryWgutils();
|
||||
return wg && wg->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
bool Daemon::addAllowedIp(const QString &ifname, const QString &prefix) {
|
||||
WireguardUtils* wg = wgutilsFor(ifname);
|
||||
return wg && wg->updateRoutePrefix(IPAddress(prefix));
|
||||
}
|
||||
|
||||
bool Daemon::delAllowedIp(const QString &ifname, const QString &prefix) {
|
||||
WireguardUtils* wg = wgutilsFor(ifname);
|
||||
return wg && wg->deleteRoutePrefix(IPAddress(prefix));
|
||||
}
|
||||
|
||||
bool Daemon::setTunnelResolvers(const QString &ifname, const QStringList &resolvers) {
|
||||
WireguardUtils* wg = wgutilsFor(ifname);
|
||||
if (!wg || !dnsutils()) {
|
||||
return false;
|
||||
}
|
||||
QList<QHostAddress> hostAddrs;
|
||||
for (const QString& r : resolvers) {
|
||||
hostAddrs.append(QHostAddress(r));
|
||||
}
|
||||
return dnsutils()->updateResolvers(wg->interfaceName(), hostAddrs);
|
||||
}
|
||||
|
||||
bool Daemon::restoreTunnelResolvers() {
|
||||
return dnsutils() && dnsutils()->restoreResolvers();
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -440,17 +457,16 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
config.m_ifname = obj.value("ifname").toString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::deactivate(bool emitSignals) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
const QString primary = m_primaryIfname;
|
||||
|
||||
// Deactivate the main interface.
|
||||
if (!m_connections.isEmpty()) {
|
||||
const ConnectionState& state = m_connections.first();
|
||||
if (!run(Down, state.m_config)) {
|
||||
if (m_connections.contains(primary)) {
|
||||
if (!run(Down, m_connections.value(primary).m_config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -459,31 +475,26 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
// Cleanup DNS
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
logger.warning() << "Failed to restore DNS resolvers.";
|
||||
}
|
||||
|
||||
// Cleanup peers and routing
|
||||
for (const ConnectionState& state : m_connections) {
|
||||
const InterfaceConfig& config = state.m_config;
|
||||
logger.debug() << "Deleting routes for" << config.m_hopType;
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
const QStringList ifnames = m_tunnels.keys();
|
||||
for (const QString& ifname : ifnames) {
|
||||
if (ifname != primary) {
|
||||
deactivateTunnel(ifname);
|
||||
}
|
||||
wgutils()->deletePeer(config);
|
||||
}
|
||||
|
||||
// Cleanup routing for excluded addresses.
|
||||
for (auto iterator = m_excludedAddrSet.constBegin();
|
||||
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
|
||||
wgutils()->deleteExclusionRoute(iterator.key());
|
||||
if (auto* wg = primaryWgutils()) {
|
||||
for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
|
||||
wg->deleteExclusionRoute(prefix);
|
||||
}
|
||||
}
|
||||
m_excludedAddrSet.clear();
|
||||
|
||||
m_connections.clear();
|
||||
// Delete the interface
|
||||
return wgutils()->deleteInterface();
|
||||
if (m_tunnels.contains(primary)) {
|
||||
deactivateTunnel(primary);
|
||||
}
|
||||
|
||||
m_activationTimer.stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
@@ -492,79 +503,18 @@ QString Daemon::logs() {
|
||||
|
||||
void Daemon::cleanLogs() { }
|
||||
|
||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
if (!m_connections.contains(config.m_hopType)) {
|
||||
return false;
|
||||
}
|
||||
const InterfaceConfig& current =
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
return current.m_privateKey == config.m_privateKey &&
|
||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||
current.m_deviceIpv6Address == config.m_deviceIpv6Address &&
|
||||
current.m_serverIpv4Gateway == config.m_serverIpv4Gateway &&
|
||||
current.m_serverIpv6Gateway == config.m_serverIpv6Gateway;
|
||||
}
|
||||
|
||||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Switching server for" << config.m_hopType;
|
||||
|
||||
Q_ASSERT(m_connections.contains(config.m_hopType));
|
||||
const InterfaceConfig& lastConfig =
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
// Configure routing for new excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Activate the new peer and its routes.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
logger.error() << "Server switch failed to update the wireguard interface";
|
||||
return false;
|
||||
}
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.error() << "Server switch failed to update the routing table";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove routing entries for the old peer.
|
||||
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||
delExclusionRoute(QHostAddress(i));
|
||||
}
|
||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old peer if it is no longer necessary.
|
||||
if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) {
|
||||
if (!wgutils()->deletePeer(lastConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject Daemon::getStatus() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
QJsonObject json;
|
||||
logger.debug() << "Status request";
|
||||
|
||||
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||
WireguardUtils* wg = primaryWgutils();
|
||||
if (!wg || !wg->interfaceExists() || !m_connections.contains(m_primaryIfname)) {
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
const ConnectionState& connection = m_connections.first();
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
const ConnectionState& connection = m_connections.value(m_primaryIfname);
|
||||
QList<WireguardUtils::PeerStatus> peers = wg->getPeerStatus();
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||
continue;
|
||||
@@ -584,38 +534,50 @@ QJsonObject Daemon::getStatus() {
|
||||
return json;
|
||||
}
|
||||
|
||||
void Daemon::checkHandshake() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
void Daemon::checkActivations() {
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
QStringList timedOut;
|
||||
bool anyPending = false;
|
||||
|
||||
logger.debug() << "Checking for handshake...";
|
||||
for (auto it = m_connections.begin(); it != m_connections.end(); ++it) {
|
||||
const QString& ifname = it.key();
|
||||
ConnectionState& cs = it.value();
|
||||
if (cs.m_date.isValid()) {
|
||||
continue; // already handshaked
|
||||
}
|
||||
logger.debug() << "awaiting" << cs.m_config.m_serverPublicKey;
|
||||
|
||||
int pendingHandshakes = 0;
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (ConnectionState& connection : m_connections) {
|
||||
const InterfaceConfig& config = connection.m_config;
|
||||
if (connection.m_date.isValid()) {
|
||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
||||
bool handshaked = false;
|
||||
if (wg) {
|
||||
for (const WireguardUtils::PeerStatus& status : wg->getPeerStatus()) {
|
||||
if (status.m_pubkey != cs.m_config.m_serverPublicKey) {
|
||||
continue;
|
||||
}
|
||||
if (status.m_handshake != 0) {
|
||||
cs.m_date.setMSecsSinceEpoch(status.m_handshake);
|
||||
emit tunnelConnected(ifname, status.m_pubkey);
|
||||
handshaked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handshaked) {
|
||||
continue;
|
||||
}
|
||||
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||
|
||||
// Check if the handshake has completed.
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (config.m_serverPublicKey != status.m_pubkey) {
|
||||
continue;
|
||||
}
|
||||
if (status.m_handshake != 0) {
|
||||
connection.m_date.setMSecsSinceEpoch(status.m_handshake);
|
||||
emit connected(status.m_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!connection.m_date.isValid()) {
|
||||
pendingHandshakes++;
|
||||
if (cs.m_deadline.isValid() && now > cs.m_deadline) {
|
||||
timedOut.append(ifname);
|
||||
} else {
|
||||
anyPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check again if there were connections that haven't completed a handshake.
|
||||
if (pendingHandshakes > 0) {
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
for (const QString& ifname : timedOut) {
|
||||
logger.warning() << "Tunnel handshake timed out:" << m_tunnels.value(ifname)->interfaceName();
|
||||
emit tunnelHandshakeFailed(ifname);
|
||||
deactivateTunnel(ifname);
|
||||
}
|
||||
|
||||
if (!anyPending) {
|
||||
m_activationTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
+25
-18
@@ -22,7 +22,6 @@ class Daemon : public QObject {
|
||||
enum Op {
|
||||
Up,
|
||||
Down,
|
||||
Switch,
|
||||
};
|
||||
|
||||
explicit Daemon(QObject* parent);
|
||||
@@ -32,10 +31,22 @@ class Daemon : public QObject {
|
||||
|
||||
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
||||
|
||||
virtual bool activate(const InterfaceConfig& config);
|
||||
bool activate(const QString& ifname, const InterfaceConfig& config);
|
||||
bool setPrimary(const QString& ifname, const InterfaceConfig& config);
|
||||
bool deactivateTunnel(const QString& ifname);
|
||||
virtual bool deactivate(bool emitSignals = true);
|
||||
virtual QJsonObject getStatus();
|
||||
|
||||
bool addExclusionRoute(const QString &ifname, const QString &addr);
|
||||
bool delExclusionRoute(const QString &ifname, const QString &addr);
|
||||
bool addAllowedIp(const QString &ifname, const QString &prefix);
|
||||
bool delAllowedIp(const QString &ifname, const QString &prefix);
|
||||
bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers);
|
||||
bool restoreTunnelResolvers();
|
||||
|
||||
const QString& primaryIfname() const { return m_primaryIfname; }
|
||||
WireguardUtils* wgutilsFor(const QString& ifname) const { return m_tunnels.value(ifname); }
|
||||
|
||||
// Callback before any Activating measure is done
|
||||
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
||||
Q_UNUSED(config) };
|
||||
@@ -46,19 +57,15 @@ class Daemon : public QObject {
|
||||
void cleanLogs();
|
||||
|
||||
signals:
|
||||
void connected(const QString& pubkey);
|
||||
/**
|
||||
* Can be fired if a call to activate() was unsucessfull
|
||||
* and connected systems should rollback
|
||||
*/
|
||||
void activationFailure();
|
||||
void tunnelConnected(const QString& ifname, const QString& pubkey);
|
||||
void tunnelHandshakeFailed(const QString& ifname);
|
||||
void disconnected();
|
||||
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
bool addExclusionRoute(const IPAddress& address);
|
||||
bool delExclusionRoute(const IPAddress& address);
|
||||
void checkActivations();
|
||||
WireguardUtils* primaryWgutils() const { return m_tunnels.value(m_primaryIfname); }
|
||||
QTimer m_activationTimer;
|
||||
|
||||
protected:
|
||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||
@@ -66,9 +73,11 @@ class Daemon : public QObject {
|
||||
Q_UNUSED(config);
|
||||
return true;
|
||||
}
|
||||
virtual bool supportServerSwitching(const InterfaceConfig& config) const;
|
||||
virtual bool switchServer(const InterfaceConfig& config);
|
||||
virtual WireguardUtils* wgutils() const = 0;
|
||||
virtual WireguardUtils* createWgUtils() = 0;
|
||||
|
||||
QMap<QString, WireguardUtils*> m_tunnels;
|
||||
QString m_primaryIfname;
|
||||
|
||||
virtual bool supportIPUtils() const { return false; }
|
||||
virtual IPUtils* iputils() { return nullptr; }
|
||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||
@@ -76,18 +85,16 @@ class Daemon : public QObject {
|
||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||
QStringList& list);
|
||||
|
||||
void checkHandshake();
|
||||
|
||||
class ConnectionState {
|
||||
public:
|
||||
ConnectionState(){};
|
||||
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
||||
QDateTime m_date;
|
||||
QDateTime m_deadline;
|
||||
InterfaceConfig m_config;
|
||||
};
|
||||
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||
QMap<QString, ConnectionState> m_connections;
|
||||
QHash<IPAddress, int> m_excludedAddrSet;
|
||||
QTimer m_handshakeTimer;
|
||||
};
|
||||
|
||||
#endif // DAEMON_H
|
||||
|
||||
@@ -31,8 +31,10 @@ DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
|
||||
&DaemonLocalServerConnection::readData);
|
||||
|
||||
Daemon* daemon = Daemon::instance();
|
||||
connect(daemon, &Daemon::connected, this,
|
||||
&DaemonLocalServerConnection::connected);
|
||||
connect(daemon, &Daemon::tunnelConnected,
|
||||
this, &DaemonLocalServerConnection::onTunnelConnected);
|
||||
connect(daemon, &Daemon::tunnelHandshakeFailed,
|
||||
this, &DaemonLocalServerConnection::onTunnelHandshakeFailed);
|
||||
connect(daemon, &Daemon::disconnected, this,
|
||||
&DaemonLocalServerConnection::disconnected);
|
||||
connect(daemon, &Daemon::backendFailure, this,
|
||||
@@ -107,19 +109,44 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
InterfaceConfig config;
|
||||
if (!Daemon::parseConfig(obj, config)) {
|
||||
logger.error() << "Invalid configuration";
|
||||
emit disconnected();
|
||||
disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Daemon::instance()->activate(config)) {
|
||||
if (!Daemon::instance()->activate(config.m_ifname, config)) {
|
||||
logger.error() << "Failed to activate the interface";
|
||||
emit disconnected();
|
||||
disconnected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "deactivate") {
|
||||
Daemon::instance()->deactivate(true);
|
||||
const QString ifname = obj.value("ifname").toString();
|
||||
if (!ifname.isEmpty()) {
|
||||
Daemon::instance()->deactivateTunnel(ifname);
|
||||
} else {
|
||||
Daemon::instance()->deactivate(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "setPrimary") {
|
||||
InterfaceConfig config;
|
||||
if (!Daemon::parseConfig(obj, config)) {
|
||||
logger.error() << "setPrimary: invalid configuration";
|
||||
return;
|
||||
}
|
||||
if (!Daemon::instance()->setPrimary(config.m_ifname, config)) {
|
||||
logger.error() << "setPrimary failed";
|
||||
QJsonObject reply;
|
||||
reply.insert("type", "primaryFailed");
|
||||
reply.insert("ifname", config.m_ifname);
|
||||
write(reply);
|
||||
return;
|
||||
}
|
||||
QJsonObject reply;
|
||||
reply.insert("type", "primaryReady");
|
||||
reply.insert("ifname", config.m_ifname);
|
||||
write(reply);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,10 +173,19 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
logger.warning() << "Invalid command:" << type;
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::connected(const QString& pubkey) {
|
||||
void DaemonLocalServerConnection::onTunnelConnected(const QString& ifname,
|
||||
const QString& pubkey) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "connected");
|
||||
obj.insert("pubkey", QJsonValue(pubkey));
|
||||
obj.insert("ifname", ifname);
|
||||
obj.insert("pubkey", pubkey);
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::onTunnelHandshakeFailed(const QString& ifname) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "disconnected");
|
||||
obj.insert("ifname", ifname);
|
||||
write(obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define DAEMONLOCALSERVERCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "daemonerrors.h"
|
||||
|
||||
@@ -23,7 +24,8 @@ class DaemonLocalServerConnection final : public QObject {
|
||||
|
||||
void parseCommand(const QByteArray& json);
|
||||
|
||||
void connected(const QString& pubkey);
|
||||
void onTunnelConnected(const QString& ifname, const QString& pubkey);
|
||||
void onTunnelHandshakeFailed(const QString& ifname);
|
||||
void disconnected();
|
||||
void backendFailure(DaemonError err);
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ QJsonObject InterfaceConfig::toJson() const {
|
||||
}
|
||||
json.insert("vpnDisabledApps", disabledApps);
|
||||
|
||||
json.insert("ifname", m_ifname);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
constexpr int ACTIVATION_TIMEOUT_MSEC = 30000;
|
||||
|
||||
class InterfaceConfig {
|
||||
Q_GADGET
|
||||
|
||||
@@ -57,6 +59,8 @@ class InterfaceConfig {
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
QString m_ifname;
|
||||
bool m_deferAddressSetup = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||
|
||||
class WireguardUtils : public QObject {
|
||||
@@ -35,7 +33,7 @@ class WireguardUtils : public QObject {
|
||||
virtual ~WireguardUtils() = default;
|
||||
|
||||
virtual bool interfaceExists() = 0;
|
||||
virtual QString interfaceName() { return WG_INTERFACE; }
|
||||
virtual QString interfaceName() = 0;
|
||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||
virtual bool deleteInterface() = 0;
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -114,10 +116,20 @@ target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
|
||||
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
+7
-2
@@ -1,13 +1,13 @@
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QLocalSocket>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/utils/osSignalHandler.h"
|
||||
#include "core/utils/migrations.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
@@ -47,6 +47,11 @@ int main(int argc, char *argv[])
|
||||
AmneziaApplication app(argc, argv);
|
||||
OsSignalHandler::setup();
|
||||
|
||||
ssh_init();
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
|
||||
ssh_finalize();
|
||||
});
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||
|
||||
@@ -44,6 +44,8 @@ class ControllerImpl : public QObject {
|
||||
// "disconnecting" state until the "disconnected" signal is received.
|
||||
virtual void deactivate() = 0;
|
||||
|
||||
virtual void setPrimary(const QJsonObject& config) { Q_UNUSED(config) }
|
||||
|
||||
// This method is used to retrieve the VPN tunnel status (mainly the number
|
||||
// of bytes sent and received). It's called always when the VPN tunnel is
|
||||
// active.
|
||||
@@ -71,11 +73,13 @@ class ControllerImpl : public QObject {
|
||||
void initialized(bool status, bool connected,
|
||||
const QDateTime& connectionDate);
|
||||
|
||||
// These 2 signals can be dispatched at any time.
|
||||
void connected(const QString& pubkey,
|
||||
const QDateTime& connectionTimestamp = QDateTime());
|
||||
void disconnected();
|
||||
|
||||
void primaryReady();
|
||||
void primaryFailed();
|
||||
|
||||
// This method should be emitted after a checkStatus() call.
|
||||
// "serverIpv4Gateway" is the current VPN tunnel gateway.
|
||||
// "deviceIpv4Address" is the address of the VPN client.
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace {
|
||||
Logger logger("LocalSocketController");
|
||||
}
|
||||
|
||||
LocalSocketController::LocalSocketController() {
|
||||
LocalSocketController::LocalSocketController(const QString& ifname)
|
||||
: m_ifname(ifname) {
|
||||
MZ_COUNT_CTOR(LocalSocketController);
|
||||
|
||||
m_socket = new QLocalSocket(this);
|
||||
@@ -121,7 +122,8 @@ void LocalSocketController::daemonConnected() {
|
||||
checkStatus();
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfig,
|
||||
const QString& ifname) {
|
||||
QString protocolName = rawConfig.value("protocol").toString();
|
||||
|
||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||
@@ -134,11 +136,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||
|
||||
QJsonObject json;
|
||||
json.insert("type", "activate");
|
||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||
json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey));
|
||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp));
|
||||
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
|
||||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
@@ -230,7 +230,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||
if (splitTunnelType == 2) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
@@ -292,6 +291,23 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
|
||||
}
|
||||
|
||||
json.insert("ifname", ifname);
|
||||
return json;
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject& rawConfig) {
|
||||
const QString protocolName = rawConfig.value("protocol").toString();
|
||||
const QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
|
||||
|
||||
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
|
||||
json.insert("type", "activate");
|
||||
write(json);
|
||||
}
|
||||
|
||||
void LocalSocketController::setPrimary(const QJsonObject& rawConfig) {
|
||||
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
|
||||
json.insert("type", "setPrimary");
|
||||
write(json);
|
||||
}
|
||||
|
||||
@@ -306,6 +322,7 @@ void LocalSocketController::deactivate() {
|
||||
|
||||
QJsonObject json;
|
||||
json.insert("type", "deactivate");
|
||||
json.insert("ifname", m_ifname);
|
||||
write(json);
|
||||
emit disconnected();
|
||||
}
|
||||
@@ -471,12 +488,20 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto belongsToThisTunnel = [this, &obj]() {
|
||||
const QJsonValue val = obj.value("ifname");
|
||||
return !val.isString() || val.toString() == m_ifname;
|
||||
};
|
||||
|
||||
if (type == "disconnected") {
|
||||
if (!belongsToThisTunnel()) return;
|
||||
disconnectInternal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "connected") {
|
||||
if (!belongsToThisTunnel()) return;
|
||||
|
||||
QJsonValue pubkey = obj.value("pubkey");
|
||||
if (!pubkey.isString()) {
|
||||
logger.error() << "Unexpected pubkey value";
|
||||
@@ -494,6 +519,18 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "primaryReady") {
|
||||
if (!belongsToThisTunnel()) return;
|
||||
emit primaryReady();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "primaryFailed") {
|
||||
if (!belongsToThisTunnel()) return;
|
||||
emit primaryFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "backendFailure") {
|
||||
if (!obj.contains("errorCode")) {
|
||||
// report a generic error if we dont know what it is.
|
||||
|
||||
@@ -19,7 +19,7 @@ class LocalSocketController final : public ControllerImpl {
|
||||
Q_DISABLE_COPY_MOVE(LocalSocketController)
|
||||
|
||||
public:
|
||||
LocalSocketController();
|
||||
explicit LocalSocketController(const QString& ifname);
|
||||
~LocalSocketController();
|
||||
|
||||
void initialize(const Device* device, const Keys* keys) override;
|
||||
@@ -28,6 +28,8 @@ class LocalSocketController final : public ControllerImpl {
|
||||
|
||||
void deactivate() override;
|
||||
|
||||
void setPrimary(const QJsonObject& rawConfig) override;
|
||||
|
||||
void checkStatus() override;
|
||||
|
||||
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
|
||||
@@ -36,6 +38,10 @@ class LocalSocketController final : public ControllerImpl {
|
||||
|
||||
bool multihopSupported() override { return true; }
|
||||
|
||||
public:
|
||||
static QJsonObject buildActivateJson(const QJsonObject& rawConfig,
|
||||
const QString& ifname);
|
||||
|
||||
private:
|
||||
void initializeInternal();
|
||||
void disconnectInternal();
|
||||
@@ -59,6 +65,7 @@ class LocalSocketController final : public ControllerImpl {
|
||||
|
||||
QByteArray m_buffer;
|
||||
|
||||
QString m_ifname;
|
||||
QString m_deviceIpv4;
|
||||
std::function<void(const QString&)> m_logCallback = nullptr;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||
|
||||
// Setup the interface to interact with
|
||||
struct ifreq ifr;
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
||||
|
||||
// MTU
|
||||
// FIXME: We need to know how many layers deep this particular
|
||||
@@ -76,7 +76,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
||||
|
||||
// Name the interface and set family
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
||||
ifr.ifr_addr.sa_family = AF_INET;
|
||||
|
||||
// Get the device address to add to interface
|
||||
@@ -126,7 +126,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
|
||||
// Get the index of named ifr and link with ifr6
|
||||
struct ifreq ifr;
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
||||
ifr.ifr_addr.sa_family = AF_INET6;
|
||||
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
|
||||
if (ret) {
|
||||
|
||||
@@ -28,7 +28,6 @@ LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
m_wgutils = new WireguardUtilsLinux(this);
|
||||
m_dnsutils = new DnsUtilsLinux(this);
|
||||
m_iputils = new IPUtilsLinux(this);
|
||||
|
||||
@@ -50,3 +49,4 @@ LinuxDaemon* LinuxDaemon::instance() {
|
||||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include "wireguardutilslinux.h"
|
||||
|
||||
class LinuxDaemon final : public Daemon {
|
||||
friend class IPUtilsMacos;
|
||||
|
||||
public:
|
||||
LinuxDaemon();
|
||||
~LinuxDaemon();
|
||||
@@ -21,13 +19,15 @@ class LinuxDaemon final : public Daemon {
|
||||
static LinuxDaemon* instance();
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
WireguardUtils* createWgUtils() override {
|
||||
return new WireguardUtilsLinux(this);
|
||||
}
|
||||
|
||||
private:
|
||||
WireguardUtilsLinux* m_wgutils = nullptr;
|
||||
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||
IPUtilsLinux* m_iputils = nullptr;
|
||||
};
|
||||
|
||||
@@ -193,8 +193,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
|
||||
QStringList result;
|
||||
for (const QString& server : servers)
|
||||
{
|
||||
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o amn+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o amn+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
@@ -278,7 +278,7 @@ void LinuxFirewall::install()
|
||||
});
|
||||
|
||||
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
||||
QStringLiteral("-o amn0+ -j ACCEPT"),
|
||||
QStringLiteral("-o amn+ -j ACCEPT"),
|
||||
QStringLiteral("-o tun0+ -j ACCEPT"),
|
||||
QStringLiteral("-o tun2+ -j ACCEPT"),
|
||||
});
|
||||
|
||||
@@ -37,33 +37,6 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
// Descriptor for a set of firewall rules to be appled.
|
||||
//
|
||||
struct FirewallParams
|
||||
{
|
||||
QStringList dnsServers;
|
||||
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
||||
QStringList allowAddrs;
|
||||
QStringList blockAddrs;
|
||||
// The follow flags indicate which general rulesets are needed. Note that
|
||||
// this is after some sanity filtering, i.e. an allow rule may be listed
|
||||
// as not needed if there were no block rules preceding it. The rulesets
|
||||
// should be thought of as in last-match order.
|
||||
|
||||
bool blockAll; // Block all traffic by default
|
||||
bool allowVPN; // Exempt traffic through VPN tunnel
|
||||
bool allowDHCP; // Exempt DHCP traffic
|
||||
bool blockIPv6; // Block all IPv6 traffic
|
||||
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
||||
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
||||
bool allowPIA; // Exempt PIA executables
|
||||
bool allowLoopback; // Exempt loopback traffic
|
||||
bool allowHnsd; // Exempt Handshake DNS traffic
|
||||
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
||||
bool allowNets;
|
||||
bool blockNets;
|
||||
};
|
||||
|
||||
class LinuxFirewall
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -39,8 +39,6 @@ typedef struct wg_allowedip {
|
||||
struct wg_allowedip *next_allowedip;
|
||||
} wg_allowedip;
|
||||
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||
int attrtype, const void* attrdata,
|
||||
size_t attrlen);
|
||||
@@ -55,6 +53,8 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
|
||||
MZ_COUNT_CTOR(LinuxRouteMonitor);
|
||||
logger.debug() << "LinuxRouteMonitor created.";
|
||||
|
||||
m_physicalGateway = NetworkUtilities::getGatewayAndIface().first;
|
||||
|
||||
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
if (m_nlsock < 0) {
|
||||
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
||||
@@ -63,7 +63,7 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
nladdr.nl_pid = getpid();
|
||||
nladdr.nl_pid = 0;
|
||||
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
||||
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||
}
|
||||
@@ -153,7 +153,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||
}
|
||||
|
||||
if (rtm->rtm_type == RTN_UNICAST) {
|
||||
int index = if_nametoindex(WG_INTERFACE);
|
||||
int index = if_nametoindex(m_ifname.toUtf8().constData());
|
||||
|
||||
if (index <= 0) {
|
||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||
@@ -164,14 +164,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
||||
}
|
||||
|
||||
if (rtm->rtm_type == RTN_THROW) {
|
||||
QString gateway = NetworkUtilities::getGatewayAndIface().first;
|
||||
if (gateway.isEmpty()) {
|
||||
logger.warning() << "No default gateway available, skipping exclusion route";
|
||||
return false;
|
||||
if (action == RTM_NEWROUTE) {
|
||||
if (m_physicalGateway.isEmpty()) {
|
||||
logger.warning() << "No physical gateway available, skipping exclusion route";
|
||||
return false;
|
||||
}
|
||||
struct in_addr ip4;
|
||||
inet_pton(AF_INET, m_physicalGateway.toUtf8(), &ip4);
|
||||
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||
}
|
||||
struct in_addr ip4;
|
||||
inet_pton(AF_INET, gateway.toUtf8(), &ip4);
|
||||
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||
rtm->rtm_type = RTN_UNICAST;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class LinuxRouteMonitor final : public QObject {
|
||||
bool rtmSendRoute(int action, int flags, int type,
|
||||
const IPAddress& prefix);
|
||||
QString m_ifname;
|
||||
QString m_physicalGateway;
|
||||
unsigned int m_ifindex = 0;
|
||||
int m_nlsock = -1;
|
||||
int m_nlseq = 0;
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QLocalSocket>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
|
||||
#include "linuxfirewall.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
@@ -59,19 +57,20 @@ void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||
logger.warning() << "Unable to start: tunnel process already running";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString ifname = config.m_ifname;
|
||||
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
if (!wgRuntimeDir.exists()) {
|
||||
wgRuntimeDir.mkpath(".");
|
||||
}
|
||||
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".sock");
|
||||
QString wgNameFile = wgRuntimeDir.filePath(ifname + ".sock");
|
||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||
#ifdef MZ_DEBUG
|
||||
pe.insert("LOG_LEVEL", "debug");
|
||||
@@ -79,7 +78,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
m_tunnel.setProcessEnvironment(pe);
|
||||
|
||||
QDir appPath(QCoreApplication::applicationDirPath());
|
||||
QStringList wgArgs = {"-f", "amn0"};
|
||||
QStringList wgArgs = {"-f", ifname};
|
||||
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
|
||||
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "Unable to start tunnel process due to timeout";
|
||||
@@ -147,29 +146,6 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_primaryDnsServer);
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||
}
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
}
|
||||
|
||||
return (err == 0);
|
||||
@@ -194,10 +170,8 @@ bool WireguardUtilsLinux::deleteInterface() {
|
||||
|
||||
// Garbage collect.
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
QFile::remove(wgRuntimeDir.filePath(m_ifname + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,13 +208,6 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
@@ -252,13 +219,6 @@ bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
// Clear exclustion routes for this peer.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
@@ -338,7 +298,7 @@ bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
|
||||
return false;
|
||||
}
|
||||
if (prefix.prefixLength() > 0) {
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
return m_rtmonitor->deleteRoute(prefix);
|
||||
}
|
||||
|
||||
// Ensure that we do not replace the default route.
|
||||
@@ -389,13 +349,9 @@ bool WireguardUtilsLinux::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||
|
||||
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||
QLocalSocket socket;
|
||||
QTimer uapiTimeout;
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||
|
||||
uapiTimeout.setSingleShot(true);
|
||||
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||
|
||||
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||
@@ -410,13 +366,15 @@ QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||
}
|
||||
socket.write(message);
|
||||
|
||||
QElapsedTimer elapsed;
|
||||
elapsed.start();
|
||||
QByteArray reply;
|
||||
while (!reply.contains("\n\n")) {
|
||||
if (!uapiTimeout.isActive()) {
|
||||
const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
|
||||
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
|
||||
logger.error() << "UAPI command timed out";
|
||||
return QString();
|
||||
}
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
reply.append(socket.readAll());
|
||||
}
|
||||
|
||||
@@ -442,45 +400,15 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
||||
timeout.setSingleShot(true);
|
||||
timeout.start(WG_TUN_PROC_TIMEOUT);
|
||||
|
||||
QFile file(filename);
|
||||
|
||||
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
QString ifname = "amn0";
|
||||
|
||||
// Test-connect to the UAPI socket.
|
||||
QLocalSocket sock;
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
|
||||
sock.connectToServer(sockName, QIODevice::ReadWrite);
|
||||
sock.connectToServer(filename, QIODevice::ReadWrite);
|
||||
if (sock.waitForConnected(100)) {
|
||||
return ifname;
|
||||
return QFileInfo(filename).baseName();
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
|
||||
{
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
|
||||
|
||||
// Note: rule precedence is handled inside IpTablesFirewall
|
||||
LinuxFirewall::ensureRootAnchorPriority();
|
||||
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
|
||||
LinuxFirewall::updateAllowNets(params.allowAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
|
||||
LinuxFirewall::updateBlockNets(params.blockAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
||||
LinuxFirewall::updateDNSServers(params.dnsServers);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "linuxroutemonitor.h"
|
||||
#include "linuxfirewall.h"
|
||||
|
||||
|
||||
class WireguardUtilsLinux final : public WireguardUtils {
|
||||
@@ -40,7 +39,6 @@ public:
|
||||
|
||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||
|
||||
void applyFirewallRules(FirewallParams& params);
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
||||
|
||||
@@ -185,6 +185,9 @@ bool DnsUtilsMacos::restoreResolvers() {
|
||||
}
|
||||
|
||||
void DnsUtilsMacos::backupService(const QString& uuid) {
|
||||
if (m_prevServices.contains(uuid)) {
|
||||
return;
|
||||
}
|
||||
DnsBackup backup;
|
||||
CFStringRef path = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
|
||||
@@ -39,8 +39,12 @@ bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||
if (!wg) {
|
||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||
return false;
|
||||
}
|
||||
QString ifname = wg->interfaceName();
|
||||
struct ifreq ifr;
|
||||
|
||||
// Create socket file descriptor to perform the ioctl operations on
|
||||
@@ -80,8 +84,12 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||
if (!wg) {
|
||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||
return false;
|
||||
}
|
||||
QString ifname = wg->interfaceName();
|
||||
struct ifaliasreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
||||
@@ -130,8 +138,12 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
||||
if (!wg) {
|
||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
||||
return false;
|
||||
}
|
||||
QString ifname = wg->interfaceName();
|
||||
struct in6_aliasreq ifr6;
|
||||
|
||||
// Name the interface and set family
|
||||
|
||||
@@ -28,7 +28,6 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
m_wgutils = new WireguardUtilsMacos(this);
|
||||
m_dnsutils = new DnsUtilsMacos(this);
|
||||
m_iputils = new IPUtilsMacos(this);
|
||||
|
||||
@@ -50,3 +49,4 @@ MacOSDaemon* MacOSDaemon::instance() {
|
||||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include "wireguardutilsmacos.h"
|
||||
|
||||
class MacOSDaemon final : public Daemon {
|
||||
friend class IPUtilsMacos;
|
||||
|
||||
public:
|
||||
MacOSDaemon();
|
||||
~MacOSDaemon();
|
||||
@@ -20,13 +18,15 @@ class MacOSDaemon final : public Daemon {
|
||||
static MacOSDaemon* instance();
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
WireguardUtils* createWgUtils() override {
|
||||
return new WireguardUtilsMacos(this);
|
||||
}
|
||||
|
||||
private:
|
||||
WireguardUtilsMacos* m_wgutils = nullptr;
|
||||
DnsUtilsMacos* m_dnsutils = nullptr;
|
||||
IPUtilsMacos* m_iputils = nullptr;
|
||||
};
|
||||
|
||||
@@ -36,35 +36,6 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
// Descriptor for a set of firewall rules to be appled.
|
||||
//
|
||||
struct FirewallParams
|
||||
{
|
||||
QStringList dnsServers;
|
||||
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
||||
|
||||
QStringList allowAddrs;
|
||||
QStringList blockAddrs;
|
||||
|
||||
// The follow flags indicate which general rulesets are needed. Note that
|
||||
// this is after some sanity filtering, i.e. an allow rule may be listed
|
||||
// as not needed if there were no block rules preceding it. The rulesets
|
||||
// should be thought of as in last-match order.
|
||||
|
||||
bool blockAll; // Block all traffic by default
|
||||
bool blockNets;
|
||||
bool allowNets;
|
||||
bool allowVPN; // Exempt traffic through VPN tunnel
|
||||
bool allowDHCP; // Exempt DHCP traffic
|
||||
bool blockIPv6; // Block all IPv6 traffic
|
||||
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
||||
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
||||
bool allowPIA; // Exempt PIA executables
|
||||
bool allowLoopback; // Exempt loopback traffic
|
||||
bool allowHnsd; // Exempt Handshake DNS traffic
|
||||
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
||||
};
|
||||
|
||||
class MacOSFirewall
|
||||
{
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
|
||||
|
||||
MacosRouteMonitor::~MacosRouteMonitor() {
|
||||
MZ_COUNT_DTOR(MacosRouteMonitor);
|
||||
flushExclusionRoutes();
|
||||
if (m_rtsock >= 0) {
|
||||
close(m_rtsock);
|
||||
}
|
||||
@@ -436,7 +435,15 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||
return true;
|
||||
}
|
||||
if ((action == RTM_ADD) && (errno == EEXIST)) {
|
||||
return true;
|
||||
rtm->rtm_type = RTM_DELETE;
|
||||
rtm->rtm_seq = m_rtseq++;
|
||||
write(m_rtsock, rtm, rtm->rtm_msglen);
|
||||
rtm->rtm_type = RTM_ADD;
|
||||
rtm->rtm_seq = m_rtseq++;
|
||||
len = write(m_rtsock, rtm, rtm->rtm_msglen);
|
||||
if (len == rtm->rtm_msglen) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((action == RTM_DELETE) && (errno == ESRCH)) {
|
||||
return true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QLocalSocket>
|
||||
#include <QTimer>
|
||||
@@ -16,8 +17,6 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
@@ -58,19 +57,20 @@ void WireguardUtilsMacos::tunnelErrorOccurred(QProcess::ProcessError error) {
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||
logger.warning() << "Unable to start: tunnel process already running";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString ifname = config.m_ifname;
|
||||
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
if (!wgRuntimeDir.exists()) {
|
||||
wgRuntimeDir.mkpath(".");
|
||||
}
|
||||
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name");
|
||||
QString wgNameFile = wgRuntimeDir.filePath(ifname + ".name");
|
||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||
#ifdef MZ_DEBUG
|
||||
pe.insert("LOG_LEVEL", "debug");
|
||||
@@ -92,6 +92,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
m_tunnel.kill();
|
||||
return false;
|
||||
}
|
||||
QFile::remove(wgNameFile);
|
||||
logger.debug() << "Created wireguard interface" << m_ifname;
|
||||
|
||||
// Start the routing table monitor.
|
||||
@@ -145,30 +146,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_primaryDnsServer);
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||
}
|
||||
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
}
|
||||
return (err == 0);
|
||||
}
|
||||
@@ -190,13 +167,6 @@ bool WireguardUtilsMacos::deleteInterface() {
|
||||
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
|
||||
}
|
||||
|
||||
// Garbage collect.
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,13 +204,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
@@ -252,13 +215,6 @@ bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
// Clear exclustion routes for this peer.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
@@ -389,13 +345,9 @@ bool WireguardUtilsMacos::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||
|
||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
QLocalSocket socket;
|
||||
QTimer uapiTimeout;
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||
|
||||
uapiTimeout.setSingleShot(true);
|
||||
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||
|
||||
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||
@@ -410,13 +362,15 @@ QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
}
|
||||
socket.write(message);
|
||||
|
||||
QElapsedTimer elapsed;
|
||||
elapsed.start();
|
||||
QByteArray reply;
|
||||
while (!reply.contains("\n\n")) {
|
||||
if (!uapiTimeout.isActive()) {
|
||||
const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
|
||||
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
|
||||
logger.error() << "UAPI command timed out";
|
||||
return QString();
|
||||
}
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
reply.append(socket.readAll());
|
||||
}
|
||||
|
||||
@@ -463,28 +417,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);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "macosroutemonitor.h"
|
||||
#include "macosfirewall.h"
|
||||
|
||||
class WireguardUtilsMacos final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
@@ -38,8 +37,6 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
||||
|
||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||
|
||||
void applyFirewallRules(FirewallParams& params);
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
||||
|
||||
@@ -35,14 +35,8 @@ WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
|
||||
m_firewallManager = WindowsFirewall::create(this);
|
||||
Q_ASSERT(m_firewallManager != nullptr);
|
||||
|
||||
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
|
||||
m_dnsutils = new DnsUtilsWindows(this);
|
||||
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
||||
|
||||
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
|
||||
&WindowsDaemon::monitorBackendFailure);
|
||||
connect(this, &WindowsDaemon::activationFailure,
|
||||
[this]() { m_firewallManager->disableKillSwitch(); });
|
||||
}
|
||||
|
||||
WindowsDaemon::~WindowsDaemon() {
|
||||
@@ -112,3 +106,11 @@ void WindowsDaemon::monitorBackendFailure() {
|
||||
emit backendFailure();
|
||||
deactivate();
|
||||
}
|
||||
|
||||
WireguardUtils* WindowsDaemon::createWgUtils() {
|
||||
auto utils = WireguardUtilsWindows::create(m_firewallManager, this);
|
||||
if (!utils) return nullptr;
|
||||
connect(utils.get(), &WireguardUtilsWindows::backendFailure, this,
|
||||
&WindowsDaemon::monitorBackendFailure);
|
||||
return utils.release();
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ class WindowsDaemon final : public Daemon {
|
||||
|
||||
protected:
|
||||
bool run(Op op, const InterfaceConfig& config) override;
|
||||
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
WireguardUtils* createWgUtils() override;
|
||||
|
||||
private:
|
||||
void monitorBackendFailure();
|
||||
@@ -42,7 +42,6 @@ class WindowsDaemon final : public Daemon {
|
||||
|
||||
int m_inetAdapterIndex = -1;
|
||||
|
||||
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
|
||||
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
||||
QPointer<WindowsFirewall> m_firewallManager;
|
||||
|
||||
@@ -37,11 +37,14 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
|
||||
QCoreApplication::setApplicationName("Amnezia VPN Tunnel");
|
||||
QCoreApplication::setApplicationVersion(Constants::versionString());
|
||||
|
||||
if (tokens.length() != 2) {
|
||||
logger.error() << "Expected 1 parameter only: the config file.";
|
||||
if (tokens.length() < 2 || tokens.length() > 3) {
|
||||
logger.error() << "Expected: <config> [<ifname>]";
|
||||
return 1;
|
||||
}
|
||||
QString maybeConfig = tokens.at(1);
|
||||
QString name = tokens.length() == 3 && !tokens.at(2).isEmpty()
|
||||
? tokens.at(2)
|
||||
: WireguardUtilsWindows::s_defaultInterfaceName();
|
||||
|
||||
if (!maybeConfig.startsWith("[Interface]")) {
|
||||
logger.error() << "parameter Does not seem to be a config";
|
||||
@@ -64,7 +67,6 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
|
||||
WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function");
|
||||
return 1;
|
||||
}
|
||||
auto name = WireguardUtilsWindows::s_interfaceName();
|
||||
if (!tunnelProc(maybeConfig.utf16(), name.utf16())) {
|
||||
logger.error() << "Failed to activate the tunnel service";
|
||||
return 1;
|
||||
|
||||
@@ -159,7 +159,7 @@ bool WindowsFirewall::initSublayer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||
bool WindowsFirewall::enableInterface(int vpnAdapterIndex, const QString& ifname) {
|
||||
// Checks if the FW_Rule was enabled succesfully,
|
||||
// disables the whole killswitch and returns false if not.
|
||||
#define FW_OK(rule) \
|
||||
@@ -182,31 +182,39 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||
} \
|
||||
}
|
||||
|
||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||
if (vpnAdapterIndex < 0)
|
||||
{
|
||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex
|
||||
<< "ifname:" << ifname;
|
||||
|
||||
QList<uint64_t>& perTunnel = ifname.isEmpty() ? m_globalRules
|
||||
: m_tunnelRules[ifname];
|
||||
if (vpnAdapterIndex < 0) {
|
||||
IPAddress allv4("0.0.0.0/0");
|
||||
if (!blockTrafficTo(allv4, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
return false;
|
||||
}
|
||||
IPAddress allv6("::/0");
|
||||
if (!blockTrafficTo(allv6, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
if (!blockTrafficTo(allv4, MED_WEIGHT, "Block Internet", perTunnel)) {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||
"Allow usage of VPN Adapter"));
|
||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
|
||||
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic"));
|
||||
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
||||
"Allow all for AmneziaVPN.exe"));
|
||||
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
|
||||
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
|
||||
"Allow Loopback traffic on device %1"));
|
||||
IPAddress allv6("::/0");
|
||||
if (!blockTrafficTo(allv6, MED_WEIGHT, "Block Internet", perTunnel)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||
"Allow usage of VPN Adapter", perTunnel));
|
||||
}
|
||||
|
||||
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
|
||||
if (m_globalRules.isEmpty()) {
|
||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic", m_globalRules));
|
||||
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic", m_globalRules));
|
||||
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
||||
"Allow all for AmneziaVPN.exe", m_globalRules));
|
||||
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS", m_globalRules));
|
||||
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
|
||||
"Allow Loopback traffic on device %1",
|
||||
m_globalRules));
|
||||
}
|
||||
|
||||
logger.debug() << "Killswitch on! Globals:" << m_globalRules.length()
|
||||
<< "Tunnel[" << ifname
|
||||
<< "]:" << m_tunnelRules.value(ifname).length();
|
||||
return true;
|
||||
#undef FW_OK
|
||||
}
|
||||
@@ -226,7 +234,8 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
||||
|
||||
// Blocking unprotected traffic
|
||||
for (const IPAddress& prefix : ranges) {
|
||||
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
|
||||
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic",
|
||||
m_globalRules)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +251,10 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
||||
}
|
||||
|
||||
// Allow unprotected traffic sent to the following address ranges.
|
||||
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString& ifname) {
|
||||
QList<uint64_t>& target = ifname.isEmpty() ? m_globalRules
|
||||
: m_tunnelRules[ifname];
|
||||
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
@@ -255,8 +267,9 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||
});
|
||||
|
||||
for (const QString& addr : ranges) {
|
||||
logger.debug() << "Allow killswitch exclude: " << addr;
|
||||
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
|
||||
logger.debug() << "Allow killswitch exclude: " << addr << "ifname:" << ifname;
|
||||
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT,
|
||||
"Allow killswitch bypass traffic", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -273,6 +286,10 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||
|
||||
|
||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
QList<uint64_t>& target = config.m_ifname.isEmpty()
|
||||
? m_globalRules
|
||||
: m_tunnelRules[config.m_ifname];
|
||||
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
@@ -288,12 +305,12 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
logger.info() << "Enabling traffic for peer"
|
||||
<< config.m_serverPublicKey;
|
||||
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
|
||||
"Block Internet", config.m_serverPublicKey)) {
|
||||
"Block Internet", target)) {
|
||||
return false;
|
||||
}
|
||||
if (!config.m_primaryDnsServer.isEmpty()) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
"Allow DNS-Server", target)) {
|
||||
return false;
|
||||
}
|
||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||
@@ -302,7 +319,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||
config.m_serverPublicKey)) {
|
||||
target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -310,7 +327,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
"Allow DNS-Server", target)) {
|
||||
return false;
|
||||
}
|
||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||
@@ -319,7 +336,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||
config.m_serverPublicKey)) {
|
||||
target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +345,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
for (const QString& dns : config.m_allowedDnsServers) {
|
||||
logger.debug() << "Allow DNS: " << dns;
|
||||
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
"Allow DNS-Server", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -338,7 +355,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
logger.debug() << "excludedAddresses range: " << i;
|
||||
|
||||
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
"Allow Ecxlude route", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -354,35 +371,6 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
if (result != ERROR_SUCCESS) {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
}
|
||||
});
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info() << "Disabling traffic for peer" << pubkey;
|
||||
for (const auto& filterID : m_peerRules.values(pubkey)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
m_peerRules.remove(pubkey, filterID);
|
||||
}
|
||||
|
||||
// Commit!
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::disableKillSwitch() {
|
||||
return KillSwitch::instance()->disableKillSwitch();
|
||||
}
|
||||
@@ -400,11 +388,13 @@ bool WindowsFirewall::allowAllTraffic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& filterID : m_peerRules.values()) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
for (const auto& bucket : qAsConst(m_tunnelRules)) {
|
||||
for (const auto& filterID : bucket) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||
for (const auto& filterID : qAsConst(m_globalRules)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
@@ -415,15 +405,42 @@ bool WindowsFirewall::allowAllTraffic() {
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
m_peerRules.clear();
|
||||
m_activeRules.clear();
|
||||
m_tunnelRules.clear();
|
||||
m_globalRules.clear();
|
||||
logger.debug() << "Firewall Disabled!";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::disableKillSwitchForTunnel(const QString& ifname) {
|
||||
if (ifname.isEmpty() || !m_tunnelRules.contains(ifname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QList<uint64_t> filters = m_tunnelRules.take(ifname);
|
||||
logger.info() << "Disabling killswitch filters for tunnel" << ifname
|
||||
<< "count:" << filters.length();
|
||||
for (const auto& filterID : filters) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:" << result;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
int weight,
|
||||
const QString& title) {
|
||||
const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
Q_ASSERT(weight <= 15);
|
||||
|
||||
@@ -460,7 +477,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
{
|
||||
QString desc("Permit (out) IPv4 Traffic of: " + appName);
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
if (!enableFilter(&filter, title, desc)) {
|
||||
if (!enableFilter(&filter, title, desc, target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -468,7 +485,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
{
|
||||
QString desc("Permit (in) IPv4 Traffic of: " + appName);
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
if (!enableFilter(&filter, title, desc)) {
|
||||
if (!enableFilter(&filter, title, desc, target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -476,7 +493,8 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
const QString& title) {
|
||||
const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
FWPM_FILTER_CONDITION0 conds;
|
||||
// Condition: Request must be targeting the TUN interface
|
||||
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
|
||||
@@ -498,25 +516,25 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
// #1 Permit outbound IPv4 traffic.
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("out").arg(networkAdapter))) {
|
||||
description.arg("out").arg(networkAdapter), target)) {
|
||||
return false;
|
||||
}
|
||||
// #2 Permit inbound IPv4 traffic.
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("in").arg(networkAdapter))) {
|
||||
description.arg("in").arg(networkAdapter), target)) {
|
||||
return false;
|
||||
}
|
||||
// #3 Permit outbound IPv6 traffic.
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("out").arg(networkAdapter))) {
|
||||
description.arg("out").arg(networkAdapter), target)) {
|
||||
return false;
|
||||
}
|
||||
// #4 Permit inbound IPv6 traffic.
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("in").arg(networkAdapter))) {
|
||||
description.arg("in").arg(networkAdapter), target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -524,7 +542,7 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
|
||||
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||
const QString& title,
|
||||
const QString& peer) {
|
||||
QList<uint64_t>& target) {
|
||||
GUID layerKeyOut;
|
||||
GUID layerKeyIn;
|
||||
if (addr.type() == QAbstractSocket::IPv4Protocol) {
|
||||
@@ -562,11 +580,11 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||
// 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)) {
|
||||
if (!enableFilter(&filter, title, description.arg("to"), target)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerKeyIn;
|
||||
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
|
||||
if (!enableFilter(&filter, title, description.arg("from"), target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -574,7 +592,7 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||
|
||||
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||
int weight, const QString& title,
|
||||
const QString& peer) {
|
||||
QList<uint64_t>& target) {
|
||||
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||
GUID layerOut =
|
||||
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
@@ -623,19 +641,20 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||
filter.layerKey = layerOut;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("to").arg(targetIP.toString()).arg(port),
|
||||
peer)) {
|
||||
target)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerIn;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("from").arg(targetIP.toString()).arg(port),
|
||||
peer)) {
|
||||
target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
// Allow outbound DHCPv4
|
||||
{
|
||||
FWPM_FILTER_CONDITION0 conds[4];
|
||||
@@ -672,7 +691,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
|
||||
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) {
|
||||
if (!enableFilter(&filter, title, "Allow Outbound DHCP", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -705,7 +724,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
|
||||
if (!enableFilter(&filter, title, "Allow inbound DHCP")) {
|
||||
if (!enableFilter(&filter, title, "Allow inbound DHCP", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -740,7 +759,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
|
||||
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) {
|
||||
if (!enableFilter(&filter, title, "Allow outbound DHCPv6", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -773,7 +792,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
filter.weight.uint8 = weight;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
if (!enableFilter(&filter, title, "Allow inbound DHCPv6")) {
|
||||
if (!enableFilter(&filter, title, "Allow inbound DHCPv6", target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -781,7 +800,8 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
}
|
||||
|
||||
// Allows the internal Hyper-V Switches to work.
|
||||
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
|
||||
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
FWPM_FILTER_CONDITION0 cond;
|
||||
// Condition: Request must be targeting the TUN interface
|
||||
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
|
||||
@@ -801,12 +821,12 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
|
||||
|
||||
// #1 Permit Hyper-V => Hyper-V outbound.
|
||||
filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE;
|
||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound")) {
|
||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound", target)) {
|
||||
return false;
|
||||
}
|
||||
// #2 Permit Hyper-V => Hyper-V inbound.
|
||||
filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE;
|
||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound")) {
|
||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound", target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -814,7 +834,7 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
|
||||
|
||||
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title,
|
||||
const QString& peer) {
|
||||
QList<uint64_t>& target) {
|
||||
QString description("Block traffic %1 %2 ");
|
||||
|
||||
auto lower = addr.address();
|
||||
@@ -852,12 +872,12 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||
|
||||
filter.layerKey = layerKeyOut;
|
||||
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
||||
peer)) {
|
||||
target)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerKeyIn;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("from").arg(addr.toString()), peer)) {
|
||||
description.arg("from").arg(addr.toString()), target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -865,9 +885,9 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||
|
||||
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
|
||||
uint8_t weight, const QString& title,
|
||||
const QString& peer) {
|
||||
QList<uint64_t>& target) {
|
||||
for (auto range : rangeList) {
|
||||
if (!blockTrafficTo(range, weight, title, peer)) {
|
||||
if (!blockTrafficTo(range, weight, title, target)) {
|
||||
logger.info() << "Setting Range of" << range.toString() << "failed";
|
||||
return false;
|
||||
}
|
||||
@@ -923,7 +943,8 @@ void WindowsFirewall::importAddress(const QHostAddress& addr,
|
||||
}
|
||||
|
||||
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
||||
const QString& title) {
|
||||
const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
// Allow Traffic to IP with PORT using any protocol
|
||||
FWPM_FILTER_CONDITION0 conds[3];
|
||||
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||
@@ -953,20 +974,20 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
||||
|
||||
QString description("Block %1 on Port %2");
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port))) {
|
||||
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port), target)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) {
|
||||
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port), target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) {
|
||||
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port), target)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) {
|
||||
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port), target)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -974,7 +995,7 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
||||
|
||||
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||
const QString& description,
|
||||
const QString& peer) {
|
||||
QList<uint64_t>& target) {
|
||||
uint64_t filterID = 0;
|
||||
auto name = title.toStdWString();
|
||||
auto desc = description.toStdWString();
|
||||
@@ -987,16 +1008,12 @@ bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||
return false;
|
||||
}
|
||||
logger.info() << "Filter added: " << title << ":" << description;
|
||||
if (peer.isEmpty()) {
|
||||
m_activeRules.append(filterID);
|
||||
} else {
|
||||
m_peerRules.insert(peer, filterID);
|
||||
}
|
||||
target.append(filterID);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
|
||||
const QString& title) {
|
||||
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target) {
|
||||
QList<QNetworkInterface> networkInterfaces =
|
||||
QNetworkInterface::allInterfaces();
|
||||
for (const auto& iface : networkInterfaces) {
|
||||
@@ -1004,7 +1021,7 @@ bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
|
||||
continue;
|
||||
}
|
||||
if (!allowTrafficOfAdapter(iface.index(), weight,
|
||||
title.arg(iface.name()))) {
|
||||
title.arg(iface.name()), target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
@@ -38,38 +39,42 @@ class WindowsFirewall final : public QObject {
|
||||
static WindowsFirewall* create(QObject* parent);
|
||||
~WindowsFirewall() override;
|
||||
|
||||
bool enableInterface(int vpnAdapterIndex);
|
||||
bool enableInterface(int vpnAdapterIndex, const QString& ifname = QString());
|
||||
bool enableLanBypass(const QList<IPAddress>& ranges);
|
||||
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||
bool disablePeerTraffic(const QString& pubkey);
|
||||
bool disableKillSwitch();
|
||||
bool disableKillSwitchForTunnel(const QString& ifname);
|
||||
bool allowAllTraffic();
|
||||
bool allowTrafficRange(const QStringList& ranges);
|
||||
bool allowTrafficRange(const QStringList& ranges, const QString& ifname = QString());
|
||||
|
||||
private:
|
||||
static bool initSublayer();
|
||||
WindowsFirewall(HANDLE session, QObject* parent);
|
||||
HANDLE m_sessionHandle;
|
||||
bool m_init = false;
|
||||
QList<uint64_t> m_activeRules;
|
||||
QMultiMap<QString, uint64_t> m_peerRules;
|
||||
QList<uint64_t> m_globalRules;
|
||||
QMap<QString, QList<uint64_t>> m_tunnelRules;
|
||||
|
||||
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
|
||||
const QString& title);
|
||||
const QString& title, QList<uint64_t>& target);
|
||||
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
|
||||
const QString& title, const QString& peer = QString());
|
||||
const QString& title, QList<uint64_t>& target);
|
||||
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);
|
||||
const QString& title, QList<uint64_t>& target);
|
||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target);
|
||||
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
|
||||
const QString& peer = QString());
|
||||
QList<uint64_t>& target);
|
||||
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||
const QString& title, const QString& peer = QString());
|
||||
const QString& title, QList<uint64_t>& target);
|
||||
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
const QString& title);
|
||||
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||
bool allowHyperVTraffic(uint8_t weight, const QString& title);
|
||||
bool allowLoopbackTraffic(uint8_t weight, const QString& title);
|
||||
const QString& title, QList<uint64_t>& target);
|
||||
bool allowDHCPTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target);
|
||||
bool allowHyperVTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target);
|
||||
bool allowLoopbackTraffic(uint8_t weight, const QString& title,
|
||||
QList<uint64_t>& target);
|
||||
|
||||
// Utils
|
||||
QString getCurrentPath();
|
||||
@@ -78,8 +83,7 @@ class WindowsFirewall final : public QObject {
|
||||
void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value,
|
||||
OUT QByteArray* v6DataBuffer);
|
||||
bool enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||
const QString& description,
|
||||
const QString& peer = QString());
|
||||
const QString& description, QList<uint64_t>& target);
|
||||
};
|
||||
|
||||
#endif // WINDOWSFIREWALL_H
|
||||
|
||||
@@ -58,9 +58,12 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QSet<quint64> WindowsRouteMonitor::s_vpnLuids;
|
||||
|
||||
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||
: QObject(parent), m_luid(luid) {
|
||||
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||
s_vpnLuids.insert(luid);
|
||||
logger.debug() << "WindowsRouteMonitor created.";
|
||||
|
||||
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
||||
@@ -69,8 +72,8 @@ WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||
CancelMibChangeNotify2(m_routeHandle);
|
||||
s_vpnLuids.remove(m_luid);
|
||||
|
||||
flushRouteTable(m_exclusionRoutes);
|
||||
flushRouteTable(m_clonedRoutes);
|
||||
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||
}
|
||||
@@ -95,7 +98,8 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
|
||||
// Rebuild the list of interfaces that are valid for routing.
|
||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
||||
if (row->InterfaceLuid.Value == m_luid) {
|
||||
// Skip any VPN wintun (own or sibling) so exclusion routes never pick one.
|
||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
if (!row->Connected) {
|
||||
@@ -126,8 +130,8 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
||||
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||
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) {
|
||||
// Skip any VPN wintun (own or sibling).
|
||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||
@@ -239,14 +243,16 @@ QHostAddress WindowsRouteMonitor::prefixToAddress(
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
bool WindowsRouteMonitor::isRouteExcluded(void* ptable,
|
||||
const IP_ADDRESS_PREFIX* dest) const {
|
||||
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||
const MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -272,8 +278,8 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
||||
|
||||
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) {
|
||||
// Skip any VPN wintun (own or sibling).
|
||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
// Ignore the default route
|
||||
@@ -286,7 +292,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
||||
continue;
|
||||
}
|
||||
// Ignore routes which should be excluded.
|
||||
if (isRouteExcluded(&row->DestinationPrefix)) {
|
||||
if (isRouteExcluded(table, &row->DestinationPrefix)) {
|
||||
continue;
|
||||
}
|
||||
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
||||
@@ -375,11 +381,6 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_exclusionRoutes.contains(prefix)) {
|
||||
logger.warning() << "Exclusion route already exists";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate and initialize the MIB routing table row.
|
||||
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
||||
InitializeIpForwardEntry(data);
|
||||
@@ -427,8 +428,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
updateCapturedRoutes(family, table);
|
||||
updateExclusionRoute(data, table);
|
||||
FreeMibTable(table);
|
||||
|
||||
m_exclusionRoutes[prefix] = data;
|
||||
delete data;
|
||||
m_ownedExclusionRoutes.insert(prefix);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -436,23 +437,39 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< prefix.address().toString();
|
||||
|
||||
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||
if (data == nullptr) {
|
||||
return true;
|
||||
m_ownedExclusionRoutes.remove(prefix);
|
||||
|
||||
PMIB_IPFORWARD_TABLE2 table;
|
||||
DWORD result = GetIpForwardTable2(AF_UNSPEC, &table);
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to fetch routing table:" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||
logger.error() << "Failed to delete route to"
|
||||
<< prefix.toString()
|
||||
<< "result:" << result;
|
||||
const bool isV4 = prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
|
||||
const ADDRESS_FAMILY addrFamily =
|
||||
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
|
||||
: static_cast<ADDRESS_FAMILY>(AF_INET6);
|
||||
bool deleted = false;
|
||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
|
||||
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
|
||||
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
|
||||
DWORD r = DeleteIpForwardEntry2(row);
|
||||
if (r == NO_ERROR || r == ERROR_NOT_FOUND) {
|
||||
deleted = true;
|
||||
} else {
|
||||
logger.error() << "Failed to delete route to" << prefix.toString()
|
||||
<< "result:" << r;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Captured routes might have changed.
|
||||
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
|
||||
|
||||
delete data;
|
||||
return true;
|
||||
FreeMibTable(table);
|
||||
updateCapturedRoutes(addrFamily);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::flushRouteTable(
|
||||
@@ -492,8 +509,24 @@ void WindowsRouteMonitor::routeChanged() {
|
||||
|
||||
updateInterfaceMetrics(AF_UNSPEC);
|
||||
updateCapturedRoutes(AF_UNSPEC, table);
|
||||
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
||||
updateExclusionRoute(data, table);
|
||||
|
||||
for (const IPAddress& prefix : m_ownedExclusionRoutes) {
|
||||
const bool isV4 =
|
||||
prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
|
||||
const ADDRESS_FAMILY addrFamily =
|
||||
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
|
||||
: static_cast<ADDRESS_FAMILY>(AF_INET6);
|
||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
||||
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
||||
if (row->DestinationPrefix.Prefix.si_family != addrFamily) continue;
|
||||
if (row->DestinationPrefix.PrefixLength != prefix.prefixLength()) continue;
|
||||
if (prefixToAddress(&row->DestinationPrefix) != prefix.address()) continue;
|
||||
MIB_IPFORWARD_ROW2 copy = *row;
|
||||
updateExclusionRoute(©, table);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FreeMibTable(table);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
||||
@@ -28,7 +29,6 @@ class WindowsRouteMonitor final : public QObject {
|
||||
|
||||
bool addExclusionRoute(const IPAddress& prefix);
|
||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
|
||||
|
||||
quint64 getLuid() const { return m_luid; }
|
||||
|
||||
@@ -36,7 +36,7 @@ class WindowsRouteMonitor final : public QObject {
|
||||
void routeChanged();
|
||||
|
||||
private:
|
||||
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
|
||||
bool isRouteExcluded(void* table, const IP_ADDRESS_PREFIX* dest) const;
|
||||
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||
const IP_ADDRESS_PREFIX* dest);
|
||||
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
||||
@@ -47,7 +47,7 @@ class WindowsRouteMonitor final : public QObject {
|
||||
void updateCapturedRoutes(int family);
|
||||
void updateCapturedRoutes(int family, void* table);
|
||||
|
||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
||||
QSet<IPAddress> m_ownedExclusionRoutes;
|
||||
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
||||
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
||||
|
||||
@@ -57,6 +57,8 @@ class WindowsRouteMonitor final : public QObject {
|
||||
|
||||
const quint64 m_luid = 0;
|
||||
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||
|
||||
static QSet<quint64> s_vpnLuids;
|
||||
};
|
||||
|
||||
#endif /* WINDOWSROUTEMONITOR_H */
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "windowsdaemon.h"
|
||||
|
||||
#define TUNNEL_NAMED_PIPE \
|
||||
"\\\\." \
|
||||
"\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\AmneziaVPN"
|
||||
#define TUNNEL_NAMED_PIPE_PREFIX \
|
||||
"\\\\.\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\"
|
||||
|
||||
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
||||
|
||||
@@ -28,6 +27,10 @@ Logger logger("WindowsTunnelService");
|
||||
static bool stopAndDeleteTunnelService(SC_HANDLE service);
|
||||
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
|
||||
|
||||
std::wstring WindowsTunnelService::serviceNameForIfname(const QString& ifname) {
|
||||
return (QStringLiteral("AmneziaWGTunnel$") + ifname).toStdWString();
|
||||
}
|
||||
|
||||
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(WindowsTunnelService);
|
||||
logger.debug() << "WindowsTunnelService created.";
|
||||
@@ -37,7 +40,7 @@ WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
||||
WindowsUtils::windowsLog("Failed to open SCManager");
|
||||
}
|
||||
|
||||
// Is the service already running? Terminate it.
|
||||
// Is the legacy single-tunnel service still around? Terminate it.
|
||||
SC_HANDLE service =
|
||||
OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||
if (service != nullptr) {
|
||||
@@ -108,8 +111,11 @@ void WindowsTunnelService::timeout() {
|
||||
emit backendFailure();
|
||||
}
|
||||
|
||||
bool WindowsTunnelService::start(const QString& configData) {
|
||||
logger.debug() << "Starting the tunnel service";
|
||||
bool WindowsTunnelService::start(const QString& configData, const QString& ifname) {
|
||||
logger.debug() << "Starting the tunnel service for" << ifname;
|
||||
|
||||
m_ifname = ifname;
|
||||
const std::wstring serviceName = serviceNameForIfname(ifname);
|
||||
|
||||
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
|
||||
m_logworker->moveToThread(&m_logthread);
|
||||
@@ -128,10 +134,9 @@ bool WindowsTunnelService::start(const QString& configData) {
|
||||
m_logworker = nullptr;
|
||||
});
|
||||
|
||||
// Let's see if we have to delete a previous instance.
|
||||
service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||
service = OpenService(scm, serviceName.c_str(), SERVICE_ALL_ACCESS);
|
||||
if (service) {
|
||||
logger.debug() << "An existing service has been detected. Let's close it.";
|
||||
logger.debug() << "A stale service was detected. Cleaning it up.";
|
||||
if (!stopAndDeleteTunnelService(service)) {
|
||||
return false;
|
||||
}
|
||||
@@ -143,12 +148,12 @@ bool WindowsTunnelService::start(const QString& configData) {
|
||||
{
|
||||
QTextStream out(&serviceCmdline);
|
||||
out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \""
|
||||
<< configData << "\"";
|
||||
<< configData << "\" \"" << ifname << "\"";
|
||||
}
|
||||
|
||||
logger.debug() << "Service:" << qApp->applicationFilePath();
|
||||
|
||||
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amnezia VPN (tunnel)",
|
||||
service = CreateService(scm, serviceName.c_str(), L"Amnezia VPN (tunnel)",
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
|
||||
@@ -236,8 +241,9 @@ static bool stopAndDeleteTunnelService(SC_HANDLE service) {
|
||||
}
|
||||
|
||||
QString WindowsTunnelService::uapiCommand(const QString& command) {
|
||||
// Create a pipe to the tunnel service.
|
||||
LPTSTR tunnelName = (LPTSTR)TEXT(TUNNEL_NAMED_PIPE);
|
||||
const std::wstring pipeName = std::wstring(TEXT(TUNNEL_NAMED_PIPE_PREFIX))
|
||||
+ m_ifname.toStdWString();
|
||||
LPCWSTR tunnelName = pipeName.c_str();
|
||||
HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||
OPEN_EXISTING, 0, nullptr);
|
||||
if (pipe == INVALID_HANDLE_VALUE) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <string>
|
||||
|
||||
#include "windowstunnellogger.h"
|
||||
|
||||
@@ -20,11 +21,13 @@ class WindowsTunnelService final : public QObject {
|
||||
WindowsTunnelService(QObject* parent = nullptr);
|
||||
~WindowsTunnelService();
|
||||
|
||||
bool start(const QString& configData);
|
||||
bool start(const QString& configData, const QString& ifname);
|
||||
void stop();
|
||||
bool isRunning();
|
||||
QString uapiCommand(const QString& command);
|
||||
|
||||
static std::wstring serviceNameForIfname(const QString& ifname);
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
||||
@@ -36,6 +39,7 @@ class WindowsTunnelService final : public QObject {
|
||||
QTimer m_timer;
|
||||
QThread m_logthread;
|
||||
WindowsTunnelLogger* m_logworker = nullptr;
|
||||
QString m_ifname;
|
||||
|
||||
// These are really SC_HANDLEs in disguise.
|
||||
void* m_scm = nullptr;
|
||||
|
||||
@@ -102,23 +102,36 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't want to pass a peer just yet, that will happen later with
|
||||
// a UAPI command in WireguardUtilsWindows::updatePeer(), so truncate
|
||||
// the config file to remove the [Peer] section.
|
||||
qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive);
|
||||
if (peerStart >= 0) {
|
||||
configString.truncate(peerStart);
|
||||
}
|
||||
|
||||
if (!m_tunnel.start(configString)) {
|
||||
auto stripLine = [&](const QString& key) {
|
||||
qsizetype start = configString.startsWith(key + " = ")
|
||||
? 0
|
||||
: configString.indexOf("\n" + key + " = ");
|
||||
if (start < 0) return;
|
||||
if (start != 0) start += 1;
|
||||
qsizetype end = configString.indexOf('\n', start);
|
||||
if (end < 0) return;
|
||||
configString.remove(start, end - start + 1);
|
||||
};
|
||||
|
||||
stripLine("DNS");
|
||||
if (config.m_deferAddressSetup) {
|
||||
// Wintun rejects duplicate IPv4; daemon will assign at swap time.
|
||||
stripLine("Address");
|
||||
}
|
||||
|
||||
m_ifname = config.m_ifname.isEmpty() ? s_defaultInterfaceName() : config.m_ifname;
|
||||
if (!m_tunnel.start(configString, m_ifname)) {
|
||||
logger.error() << "Failed to activate the tunnel service";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine the interface LUID
|
||||
NET_LUID luid;
|
||||
QString ifAlias = interfaceName();
|
||||
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)ifAlias.utf16(), &luid);
|
||||
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)m_ifname.utf16(), &luid);
|
||||
if (result != 0) {
|
||||
logger.error() << "Failed to lookup LUID:" << result;
|
||||
return false;
|
||||
@@ -126,14 +139,6 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||
m_luid = luid.Value;
|
||||
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
|
||||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall
|
||||
NET_IFINDEX ifindex;
|
||||
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||
m_firewall->allowAllTraffic();
|
||||
m_firewall->enableInterface(ifindex);
|
||||
}
|
||||
|
||||
logger.debug() << "Registration completed";
|
||||
return true;
|
||||
}
|
||||
@@ -143,7 +148,6 @@ bool WireguardUtilsWindows::deleteInterface() {
|
||||
m_routeMonitor->deleteLater();
|
||||
}
|
||||
|
||||
m_firewall->disableKillSwitch();
|
||||
m_tunnel.stop();
|
||||
return true;
|
||||
}
|
||||
@@ -154,10 +158,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
QByteArray pskKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall for this peer.
|
||||
m_firewall->enablePeerTraffic(config);
|
||||
}
|
||||
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||
<< "via" << config.m_serverIpv4AddrIn;
|
||||
|
||||
@@ -185,12 +185,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
QString reply = m_tunnel.uapiCommand(message);
|
||||
logger.debug() << "DATA:" << reply;
|
||||
return true;
|
||||
@@ -200,15 +194,6 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
// Clear exclustion routes for this peer.
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
// Disable the windows firewall for this peer.
|
||||
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
|
||||
@@ -27,10 +27,8 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
||||
~WireguardUtilsWindows();
|
||||
|
||||
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
||||
QString interfaceName() override {
|
||||
return WireguardUtilsWindows::s_interfaceName();
|
||||
}
|
||||
static const QString s_interfaceName() { return "AmneziaVPN"; }
|
||||
QString interfaceName() override { return m_ifname; }
|
||||
static const QString s_defaultInterfaceName() { return "AmneziaVPN"; }
|
||||
bool addInterface(const InterfaceConfig& config) override;
|
||||
bool deleteInterface() override;
|
||||
|
||||
@@ -54,6 +52,7 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
||||
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
||||
|
||||
quint64 m_luid = 0;
|
||||
QString m_ifname;
|
||||
WindowsTunnelService m_tunnel;
|
||||
QPointer<WindowsRouteMonitor> m_routeMonitor;
|
||||
QPointer<WindowsFirewall> m_firewall;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
|
||||
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
|
||||
fi;\
|
||||
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
|
||||
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
|
||||
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
|
||||
else pm="uname"; opt="-a";\
|
||||
fi;\
|
||||
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
||||
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 zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
|
||||
else echo "Packet manager not found"; exit 1; fi;\
|
||||
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
|
||||
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
|
||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
|
||||
fi;\
|
||||
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
|
||||
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
|
||||
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
|
||||
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
|
||||
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
|
||||
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;\
|
||||
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
|
||||
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
|
||||
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
|
||||
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
|
||||
sudo -n $pm $check_pkgs;\
|
||||
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
|
||||
sudo -n $pm $silent_inst $docker_pkg;\
|
||||
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
|
||||
else \
|
||||
echo "Container runtime is not supported";\
|
||||
exit 1;\
|
||||
fi;\
|
||||
fi;\
|
||||
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;\
|
||||
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
|
||||
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
|
||||
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
|
||||
fi;\
|
||||
fi;\
|
||||
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;\
|
||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
|
||||
sleep 5; sudo -n systemctl start docker; sleep 5;\
|
||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
|
||||
fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
||||
docker --version;\
|
||||
sudo -n docker --version || docker --version;\
|
||||
uname -sr
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
|
||||
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
|
||||
|
||||
# Determine secret: env var -> saved file -> generate new
|
||||
if [ -n "$MTPROXY_SECRET" ]; then
|
||||
# Determine secret: regenerate (fresh install) -> env var -> saved file -> generate new
|
||||
if [ "$MTPROXY_REGENERATE_SECRET" = "1" ]; then
|
||||
SECRET=$(openssl rand -hex 16)
|
||||
elif [ -n "$MTPROXY_SECRET" ]; then
|
||||
SECRET="$MTPROXY_SECRET"
|
||||
elif [ -f /data/secret ]; then
|
||||
SECRET=$(cat /data/secret)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user