mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-24 02:00:24 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 62fa095153 |
@@ -157,7 +157,7 @@ jobs:
|
|||||||
run: pip install "conan==2.28.0"
|
run: pip install "conan==2.28.0"
|
||||||
|
|
||||||
- name: 'Build dependencies'
|
- name: 'Build dependencies'
|
||||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||||
|
|
||||||
- name: 'Authorize in remote'
|
- name: 'Authorize in remote'
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ set(HEADERS ${HEADERS}
|
|||||||
${CLIENT_ROOT_DIR}/core/utils/utilities.h
|
${CLIENT_ROOT_DIR}/core/utils/utilities.h
|
||||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
|
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
|
||||||
${CLIENT_ROOT_DIR}/core/utils/constants.h
|
${CLIENT_ROOT_DIR}/core/utils/constants.h
|
||||||
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.h
|
|
||||||
${CLIENT_ROOT_DIR}/core/tunnel.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -147,8 +145,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
|
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/vpnTrafficGuard.cpp
|
|
||||||
${CLIENT_ROOT_DIR}/core/tunnel.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
|
|||||||
@@ -484,12 +484,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SubscriptionController::restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config)
|
|
||||||
{
|
|
||||||
const QJsonObject json = config.toJson();
|
|
||||||
m_serversRepository->editServer(serverId, json, serverConfigUtils::configTypeFromJson(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||||
{
|
{
|
||||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ public:
|
|||||||
|
|
||||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||||
|
|
||||||
void restoreApiV2Config(const QString &serverId, const ApiV2ServerConfig &config);
|
|
||||||
|
|
||||||
ErrorCode deactivateDevice(const QString &serverId);
|
ErrorCode deactivateDevice(const QString &serverId);
|
||||||
|
|
||||||
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
ErrorCode deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
|||||||
m_vpnConnection(vpnConnection)
|
m_vpnConnection(vpnConnection)
|
||||||
{
|
{
|
||||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||||
connect(m_vpnConnection, &VpnConnection::serverSwitchFailed, this, &ConnectionController::serverSwitchFailed);
|
|
||||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ signals:
|
|||||||
void closeConnectionRequested();
|
void closeConnectionRequested();
|
||||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||||
void killSwitchModeChangedRequested(bool enabled);
|
void killSwitchModeChangedRequested(bool enabled);
|
||||||
void serverSwitchFailed();
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
void restoreConnectionRequested();
|
void restoreConnectionRequested();
|
||||||
|
|||||||
@@ -94,12 +94,6 @@ void CoreSignalHandlers::initErrorMessagesHandler()
|
|||||||
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
m_coreController->m_connectionController->setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::serverSwitchFailed, this, [this]() {
|
|
||||||
m_coreController->m_subscriptionUiController->revertLastCountryChange();
|
|
||||||
emit m_coreController->m_pageController->showNotificationMessage(
|
|
||||||
tr("Failed to switch server. Existing connection maintained."));
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
|
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::errorOccurred, m_coreController->m_pageController,
|
||||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
|||||||
QString ip = NetworkUtilities::getIPAddress(host);
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
if (!ip.isEmpty()) {
|
if (!ip.isEmpty()) {
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QString(), QStringList { ip });
|
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
||||||
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -836,8 +836,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
|||||||
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
||||||
|
|
||||||
if (container == DockerContainer::Awg2) {
|
if (container == DockerContainer::Awg2) {
|
||||||
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||||
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
|
QRegularExpressionMatch match = regex.match(stdOut);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
int majorVersion = match.captured(1).toInt();
|
int majorVersion = match.captured(1).toInt();
|
||||||
int minorVersion = match.captured(2).toInt();
|
int minorVersion = match.captured(2).toInt();
|
||||||
@@ -850,19 +850,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
|||||||
|
|
||||||
if (stdOut.contains("lock"))
|
if (stdOut.contains("lock"))
|
||||||
return ErrorCode::ServerPacketManagerError;
|
return ErrorCode::ServerPacketManagerError;
|
||||||
if (stdOut.contains("Container runtime is not supported"))
|
if (stdOut.contains("command not found"))
|
||||||
return ErrorCode::ServerContainerRuntimeNotSupported;
|
|
||||||
|
|
||||||
QRegularExpression notFoundRegex(
|
|
||||||
R"(^.*(?:sudo:|docker:).*not found.*$)",
|
|
||||||
QRegularExpression::MultilineOption);
|
|
||||||
|
|
||||||
if (notFoundRegex.match(stdOut).hasMatch()) {
|
|
||||||
return ErrorCode::ServerDockerFailedError;
|
return ErrorCode::ServerDockerFailedError;
|
||||||
}
|
|
||||||
|
|
||||||
if (stdOut.contains("Container runtime service not running"))
|
|
||||||
return ErrorCode::ContainerRuntimeServiceNotRunning;
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -899,7 +888,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
|
|||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||||
return ErrorCode::ServerUserDirectoryNotAccessible;
|
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||||
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
|
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||||
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
||||||
return ErrorCode::ServerUserPasswordRequired;
|
return ErrorCode::ServerUserPasswordRequired;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#include "updateController.h"
|
#include "updateController.h"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QFile>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QProcess>
|
||||||
#include <QVersionNumber>
|
#include <QVersionNumber>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QTemporaryDir>
|
|
||||||
|
|
||||||
#include "amneziaApplication.h"
|
#include "amneziaApplication.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
@@ -31,6 +32,17 @@ namespace
|
|||||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
|
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
|
||||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||||
|
// Pinned signing identity of release installers. spctl prints the origin as
|
||||||
|
// origin=Developer ID Installer: <Name> (<TEAMID>)
|
||||||
|
// We require this exact substring so a different (even validly notarized)
|
||||||
|
// package is rejected.
|
||||||
|
// TODO(#1): replace with the real release identity before shipping, e.g.
|
||||||
|
// "Developer ID Installer: Amnezia OU (XXXXXXXXXX)".
|
||||||
|
// Until set, macOS update verification fails closed (updates are rejected).
|
||||||
|
const QLatin1String kExpectedMacSigningOrigin("Developer ID Installer: SET_ME_BEFORE_RELEASE");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateController::UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
UpdateController::UpdateController(SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
||||||
@@ -123,6 +135,10 @@ void UpdateController::fetchGatewayUrl()
|
|||||||
}
|
}
|
||||||
m_baseUrl = baseUrl;
|
m_baseUrl = baseUrl;
|
||||||
|
|
||||||
|
// Expected installer SHA-256 for the requesting OS, delivered over the
|
||||||
|
// encrypted gateway channel (used by Windows/Linux verification).
|
||||||
|
m_expectedSha256 = gatewayData.value("sha256").toString().trimmed().toLower();
|
||||||
|
|
||||||
fetchVersionInfo();
|
fetchVersionInfo();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -211,6 +227,53 @@ QString UpdateController::composeDownloadUrl() const
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UpdateController::verifySha256(const QByteArray &data) const
|
||||||
|
{
|
||||||
|
if (m_expectedSha256.isEmpty()) {
|
||||||
|
logger.error() << "No expected installer checksum provided by gateway; rejecting installer";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString actual = QString::fromLatin1(QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex());
|
||||||
|
if (actual.compare(m_expectedSha256, Qt::CaseInsensitive) != 0) {
|
||||||
|
logger.error() << "Installer checksum mismatch. expected:" << m_expectedSha256 << "actual:" << actual;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||||
|
bool UpdateController::verifyMacInstallerSignature(const QString &installerPath) const
|
||||||
|
{
|
||||||
|
// Gatekeeper assessment for installer packages: the .pkg must be signed AND
|
||||||
|
// notarized under our Apple Developer ID. spctl writes its verdict to stderr.
|
||||||
|
QProcess spctl;
|
||||||
|
spctl.start(QStringLiteral("/usr/sbin/spctl"),
|
||||||
|
{ QStringLiteral("--assess"), QStringLiteral("--type"), QStringLiteral("install"),
|
||||||
|
QStringLiteral("-vv"), installerPath });
|
||||||
|
if (!spctl.waitForFinished(15000)) {
|
||||||
|
logger.error() << "spctl assessment did not finish in time";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString output = QString::fromUtf8(spctl.readAllStandardError()) + QString::fromUtf8(spctl.readAllStandardOutput());
|
||||||
|
|
||||||
|
if (spctl.exitStatus() != QProcess::NormalExit || spctl.exitCode() != 0) {
|
||||||
|
logger.error() << "spctl rejected the installer:" << output.trimmed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pin the signing identity so a different (even validly notarized) package is rejected.
|
||||||
|
if (!output.contains(kExpectedMacSigningOrigin)) {
|
||||||
|
logger.error() << "Installer signed by an unexpected identity:" << output.trimmed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void UpdateController::runInstaller()
|
void UpdateController::runInstaller()
|
||||||
{
|
{
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
@@ -227,6 +290,8 @@ void UpdateController::runInstaller()
|
|||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply]() {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
const QByteArray data = reply->readAll();
|
||||||
|
|
||||||
QFile file(kInstallerLocalPath);
|
QFile file(kInstallerLocalPath);
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
logger.error() << "Failed to open installer file for writing:" << kInstallerLocalPath << "Error:" << file.errorString();
|
logger.error() << "Failed to open installer file for writing:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||||
@@ -234,7 +299,7 @@ void UpdateController::runInstaller()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.write(reply->readAll()) == -1) {
|
if (file.write(data) == -1) {
|
||||||
logger.error() << "Failed to write installer data to file:" << kInstallerLocalPath << "Error:" << file.errorString();
|
logger.error() << "Failed to write installer data to file:" << kInstallerLocalPath << "Error:" << file.errorString();
|
||||||
file.close();
|
file.close();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
@@ -243,6 +308,25 @@ void UpdateController::runInstaller()
|
|||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
// Fail-closed: never launch an installer whose authenticity we could not verify.
|
||||||
|
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||||
|
if (!verifyMacInstallerSignature(kInstallerLocalPath)) {
|
||||||
|
logger.error() << "Installer signature verification failed; refusing to launch";
|
||||||
|
QFile::remove(kInstallerLocalPath);
|
||||||
|
emit installerVerificationFailed(tr("Update verification failed: the installer signature is invalid. The update was not launched."));
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (!verifySha256(data)) {
|
||||||
|
logger.error() << "Installer checksum verification failed; refusing to launch";
|
||||||
|
QFile::remove(kInstallerLocalPath);
|
||||||
|
emit installerVerificationFailed(tr("Update verification failed: the installer checksum does not match. The update was not launched."));
|
||||||
|
reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
#if defined(Q_OS_WINDOWS)
|
||||||
runWindowsInstaller(kInstallerLocalPath);
|
runWindowsInstaller(kInstallerLocalPath);
|
||||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateFound();
|
void updateFound();
|
||||||
|
void installerVerificationFailed(const QString &reason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void finishUpdateCheck();
|
void finishUpdateCheck();
|
||||||
@@ -43,11 +44,18 @@ private:
|
|||||||
QString m_version;
|
QString m_version;
|
||||||
QString m_releaseDate;
|
QString m_releaseDate;
|
||||||
QString m_downloadUrl;
|
QString m_downloadUrl;
|
||||||
|
QString m_expectedSha256;
|
||||||
bool m_updateCheckRunning = false;
|
bool m_updateCheckRunning = false;
|
||||||
|
|
||||||
|
// Verify the downloaded installer before launching it (fail-closed):
|
||||||
|
// Windows/Linux compare a SHA-256 delivered over the encrypted gateway channel.
|
||||||
|
bool verifySha256(const QByteArray &data) const;
|
||||||
|
|
||||||
#if defined(Q_OS_WINDOWS)
|
#if defined(Q_OS_WINDOWS)
|
||||||
int runWindowsInstaller(const QString &installerPath);
|
int runWindowsInstaller(const QString &installerPath);
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_MACOS)
|
||||||
|
// macOS verifies the .pkg Gatekeeper assessment (notarized + pinned Developer ID).
|
||||||
|
bool verifyMacInstallerSignature(const QString &installerPath) const;
|
||||||
int runMacInstaller(const QString &installerPath);
|
int runMacInstaller(const QString &installerPath);
|
||||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
int runLinuxInstaller(const QString &installerPath);
|
int runLinuxInstaller(const QString &installerPath);
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ void OpenVpnProtocol::cleanupResources()
|
|||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
}
|
}
|
||||||
m_managementServer.stop();
|
m_managementServer.stop();
|
||||||
|
|
||||||
|
#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::cleanupResources(): Failed to disable killswitch";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenVpnProtocol::stop()
|
void OpenVpnProtocol::stop()
|
||||||
@@ -177,6 +186,20 @@ ErrorCode OpenVpnProtocol::start()
|
|||||||
return lastError();
|
return lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString());
|
||||||
|
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
|
||||||
|
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||||
|
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||||
|
}
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
});
|
||||||
|
if (res != ErrorCode::NoError) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Detect default gateway
|
// Detect default gateway
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
QProcess p;
|
QProcess p;
|
||||||
@@ -331,9 +354,38 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
|
|||||||
m_vpnGateway = l.split(" ").at(2);
|
m_vpnGateway = l.split(" ").at(2);
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QThread::msleep(300);
|
QThread::msleep(300);
|
||||||
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||||
|
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||||
|
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
||||||
|
{
|
||||||
|
// killSwitch toggle
|
||||||
|
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||||
|
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||||
|
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
|
||||||
|
}
|
||||||
|
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
||||||
|
m_configData.insert("vpnGateway", m_vpnGateway);
|
||||||
|
m_configData.insert("vpnServer",
|
||||||
|
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
|
||||||
|
iface->enablePeerTraffic(m_configData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
// 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";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
|
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,19 +106,6 @@ QString VpnProtocol::vpnLocalAddress() const
|
|||||||
return m_vpnLocalAddress;
|
return m_vpnLocalAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container)
|
|
||||||
{
|
|
||||||
return container == amnezia::DockerContainer::Awg
|
|
||||||
|| container == amnezia::DockerContainer::Awg2
|
|
||||||
|| container == amnezia::DockerContainer::WireGuard;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VpnProtocol::isXrayBased(amnezia::DockerContainer container)
|
|
||||||
{
|
|
||||||
return container == amnezia::DockerContainer::Xray
|
|
||||||
|| container == amnezia::DockerContainer::SSXray;
|
|
||||||
}
|
|
||||||
|
|
||||||
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
|
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
@@ -137,14 +124,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnProtocol::setPrimary(const QJsonObject &config)
|
|
||||||
{
|
|
||||||
Q_UNUSED(config)
|
|
||||||
QMetaObject::invokeMethod(this, [this]() {
|
|
||||||
emit primaryReady();
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString VpnProtocol::routeGateway() const
|
QString VpnProtocol::routeGateway() const
|
||||||
{
|
{
|
||||||
return m_routeGateway;
|
return m_routeGateway;
|
||||||
@@ -158,7 +137,6 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState)
|
|||||||
case Vpn::ConnectionState::Preparing: return tr("Preparing");
|
case Vpn::ConnectionState::Preparing: return tr("Preparing");
|
||||||
case Vpn::ConnectionState::Connecting: return tr("Connecting...");
|
case Vpn::ConnectionState::Connecting: return tr("Connecting...");
|
||||||
case Vpn::ConnectionState::Connected: return tr("Connected");
|
case Vpn::ConnectionState::Connected: return tr("Connected");
|
||||||
case Vpn::ConnectionState::Switching: return tr("Switching...");
|
|
||||||
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
|
case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting...");
|
||||||
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
|
case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting...");
|
||||||
case Vpn::ConnectionState::Error: return tr("Error");
|
case Vpn::ConnectionState::Error: return tr("Error");
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ namespace Vpn
|
|||||||
Preparing,
|
Preparing,
|
||||||
Connecting,
|
Connecting,
|
||||||
Connected,
|
Connected,
|
||||||
Switching,
|
|
||||||
Disconnecting,
|
Disconnecting,
|
||||||
Reconnecting,
|
Reconnecting,
|
||||||
Error
|
Error
|
||||||
@@ -61,7 +60,6 @@ public:
|
|||||||
virtual bool isDisconnected() const;
|
virtual bool isDisconnected() const;
|
||||||
virtual ErrorCode start() = 0;
|
virtual ErrorCode start() = 0;
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
virtual void setPrimary(const QJsonObject& config);
|
|
||||||
|
|
||||||
Vpn::ConnectionState connectionState() const;
|
Vpn::ConnectionState connectionState() const;
|
||||||
ErrorCode lastError() const;
|
ErrorCode lastError() const;
|
||||||
@@ -73,8 +71,6 @@ public:
|
|||||||
QString vpnLocalAddress() const;
|
QString vpnLocalAddress() const;
|
||||||
|
|
||||||
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
|
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
|
||||||
static bool isWireGuardBased(amnezia::DockerContainer container);
|
|
||||||
static bool isXrayBased(amnezia::DockerContainer container);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
@@ -82,8 +78,6 @@ signals:
|
|||||||
void timeoutTimerEvent();
|
void timeoutTimerEvent();
|
||||||
void protocolError(amnezia::ErrorCode e);
|
void protocolError(amnezia::ErrorCode e);
|
||||||
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
|
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
|
||||||
void primaryReady();
|
|
||||||
void primaryFailed();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void onTimeout(); // todo: remove?
|
virtual void onTimeout(); // todo: remove?
|
||||||
|
|||||||
@@ -12,11 +12,9 @@
|
|||||||
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
|
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
|
||||||
: VpnProtocol(configuration, parent)
|
: VpnProtocol(configuration, parent)
|
||||||
{
|
{
|
||||||
const QString ifname = configuration.value("ifname").toString();
|
m_impl.reset(new LocalSocketController());
|
||||||
m_impl.reset(new LocalSocketController(ifname));
|
|
||||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||||
[this](const QString& pubkey, const QDateTime&) {
|
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||||
Q_UNUSED(pubkey)
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connected);
|
setConnectionState(Vpn::ConnectionState::Connected);
|
||||||
});
|
});
|
||||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||||
@@ -35,18 +33,12 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
|
|
||||||
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
|
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
|
||||||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
|
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
|
||||||
if (m_connectionState == Vpn::ConnectionState::Connected) {
|
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||||
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||||
connect(m_impl.get(), &ControllerImpl::primaryReady,
|
|
||||||
this, &WireguardProtocol::primaryReady);
|
|
||||||
connect(m_impl.get(), &ControllerImpl::primaryFailed,
|
|
||||||
this, &WireguardProtocol::primaryFailed);
|
|
||||||
m_impl->initialize(nullptr, nullptr);
|
m_impl->initialize(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +48,13 @@ WireguardProtocol::~WireguardProtocol()
|
|||||||
QThread::msleep(200);
|
QThread::msleep(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode WireguardProtocol::start()
|
void WireguardProtocol::stop()
|
||||||
|
{
|
||||||
|
stopMzImpl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode WireguardProtocol::startMzImpl()
|
||||||
{
|
{
|
||||||
QString protocolName = m_rawConfig.value("protocol").toString();
|
QString protocolName = m_rawConfig.value("protocol").toString();
|
||||||
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
|
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
|
||||||
@@ -64,19 +62,18 @@ ErrorCode WireguardProtocol::start()
|
|||||||
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
|
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
|
||||||
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
|
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
|
||||||
|
|
||||||
m_stopped = false;
|
|
||||||
m_impl->activate(m_rawConfig);
|
m_impl->activate(m_rawConfig);
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WireguardProtocol::stop()
|
ErrorCode WireguardProtocol::stopMzImpl()
|
||||||
{
|
{
|
||||||
if (m_stopped) return;
|
|
||||||
m_stopped = true;
|
|
||||||
m_impl->deactivate();
|
m_impl->deactivate();
|
||||||
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WireguardProtocol::setPrimary(const QJsonObject& config)
|
|
||||||
|
ErrorCode WireguardProtocol::start()
|
||||||
{
|
{
|
||||||
m_impl->setPrimary(config);
|
return startMzImpl();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ public:
|
|||||||
|
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
void setPrimary(const QJsonObject& config) override;
|
|
||||||
|
ErrorCode startMzImpl();
|
||||||
|
ErrorCode stopMzImpl();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_stopped = false;
|
|
||||||
|
|
||||||
QScopedPointer<ControllerImpl> m_impl;
|
QScopedPointer<ControllerImpl> m_impl;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
static const QString tunName = "utun22";
|
||||||
|
#else
|
||||||
|
static const QString tunName = "tun2";
|
||||||
|
#endif
|
||||||
|
|
||||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||||
{
|
{
|
||||||
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
||||||
@@ -28,16 +34,11 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
|
|||||||
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
|
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
|
||||||
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
|
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
|
||||||
|
|
||||||
m_tunName = configuration.value("tunName").toString();
|
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
|
||||||
if (m_tunName.isEmpty()) {
|
m_dnsServers.push_back(QHostAddress(primaryDns));
|
||||||
m_tunName = configuration.value("ifname").toString();
|
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
|
||||||
}
|
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString();
|
||||||
if (m_tunName.isEmpty()) {
|
m_dnsServers.push_back(QHostAddress(secondaryDns));
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
m_tunName = QStringLiteral("utun22");
|
|
||||||
#else
|
|
||||||
m_tunName = QStringLiteral("tun2");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
|
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
|
||||||
@@ -67,8 +68,6 @@ ErrorCode XrayProtocol::start()
|
|||||||
{
|
{
|
||||||
qDebug() << "XrayProtocol::start()";
|
qDebug() << "XrayProtocol::start()";
|
||||||
|
|
||||||
m_phase = Phase::Active;
|
|
||||||
|
|
||||||
// Inject SOCKS5 auth into the inbound before starting xray.
|
// Inject SOCKS5 auth into the inbound before starting xray.
|
||||||
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
||||||
amnezia::serialization::inbounds::InboundCredentials creds;
|
amnezia::serialization::inbounds::InboundCredentials creds;
|
||||||
@@ -107,7 +106,7 @@ ErrorCode XrayProtocol::start()
|
|||||||
|
|
||||||
return IpcClient::withInterface(
|
return IpcClient::withInterface(
|
||||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
auto xrayStart = iface->xrayStart(m_tunName, xrayConfigStr);
|
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||||
qCritical() << "Failed to start xray";
|
qCritical() << "Failed to start xray";
|
||||||
return ErrorCode::XrayExecutableCrashed;
|
return ErrorCode::XrayExecutableCrashed;
|
||||||
@@ -121,17 +120,24 @@ void XrayProtocol::stop()
|
|||||||
{
|
{
|
||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
|
|
||||||
if (m_phase != Phase::Active) {
|
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
return;
|
auto disableKillSwitch = iface->disableKillSwitch();
|
||||||
}
|
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
||||||
m_phase = Phase::Stopping;
|
qWarning() << "Failed to disable killswitch";
|
||||||
|
|
||||||
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
|
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
||||||
auto deleteTun = iface->deleteTun(m_tunName);
|
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);
|
||||||
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
||||||
qWarning() << "Failed to delete tun";
|
qWarning() << "Failed to delete tun";
|
||||||
|
|
||||||
auto xrayStop = iface->xrayStop(m_tunName);
|
auto xrayStop = iface->xrayStop();
|
||||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
||||||
qWarning() << "Failed to stop xray";
|
qWarning() << "Failed to stop xray";
|
||||||
});
|
});
|
||||||
@@ -156,18 +162,9 @@ void XrayProtocol::stop()
|
|||||||
m_tun2socksProcess.reset();
|
m_tun2socksProcess.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_phase = Phase::Inactive;
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XrayProtocol::setPrimary(const QJsonObject &config)
|
|
||||||
{
|
|
||||||
Q_UNUSED(config)
|
|
||||||
QMetaObject::invokeMethod(this, [this]() {
|
|
||||||
emit primaryReady();
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode XrayProtocol::startTun2Socks()
|
ErrorCode XrayProtocol::startTun2Socks()
|
||||||
{
|
{
|
||||||
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
||||||
@@ -178,23 +175,10 @@ ErrorCode XrayProtocol::startTun2Socks()
|
|||||||
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
||||||
|
|
||||||
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
||||||
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(m_tunName), "-proxy", proxyUrl });
|
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl });
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::errorOccurred, this,
|
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, 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]() {
|
[this]() {
|
||||||
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
||||||
if (!readAllStandardError.waitForFinished()) {
|
if (!readAllStandardError.waitForFinished()) {
|
||||||
@@ -233,17 +217,13 @@ ErrorCode XrayProtocol::startTun2Socks()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_phase == Phase::Active && resourceBusy
|
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||||
&& m_tun2socksRetryCount < maxTun2SocksRetries) {
|
|
||||||
m_tun2socksRetryCount++;
|
m_tun2socksRetryCount++;
|
||||||
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
|
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
|
||||||
.arg(m_tun2socksRetryCount)
|
.arg(m_tun2socksRetryCount)
|
||||||
.arg(maxTun2SocksRetries)
|
.arg(maxTun2SocksRetries)
|
||||||
.arg(tun2socksRetryDelayMs);
|
.arg(tun2socksRetryDelayMs);
|
||||||
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
|
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
|
||||||
if (m_phase != Phase::Active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
|
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
|
||||||
stop();
|
stop();
|
||||||
setLastError(err);
|
setLastError(err);
|
||||||
@@ -272,17 +252,81 @@ ErrorCode XrayProtocol::setupRouting()
|
|||||||
{
|
{
|
||||||
return IpcClient::withInterface(
|
return IpcClient::withInterface(
|
||||||
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
||||||
#ifndef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
auto createTun = iface->createTun(m_tunName, amnezia::protocols::xray::defaultLocalAddr);
|
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
||||||
|
#endif
|
||||||
|
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
||||||
qCritical() << "Failed to assign IP address for TUN";
|
qCritical() << "Failed to assign IP address for TUN";
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
Q_UNUSED(iface)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
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;
|
||||||
|
#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
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
},
|
},
|
||||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||||
|
|||||||
@@ -20,20 +20,14 @@ public:
|
|||||||
|
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
void setPrimary(const QJsonObject &config) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Phase {
|
|
||||||
Inactive,
|
|
||||||
Active,
|
|
||||||
Stopping,
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorCode setupRouting();
|
ErrorCode setupRouting();
|
||||||
ErrorCode startTun2Socks();
|
ErrorCode startTun2Socks();
|
||||||
|
|
||||||
QJsonObject m_xrayConfig;
|
QJsonObject m_xrayConfig;
|
||||||
amnezia::RouteMode m_routeMode;
|
amnezia::RouteMode m_routeMode;
|
||||||
|
QList<QHostAddress> m_dnsServers;
|
||||||
QString m_remoteAddress;
|
QString m_remoteAddress;
|
||||||
|
|
||||||
QString m_socksUser;
|
QString m_socksUser;
|
||||||
@@ -44,9 +38,6 @@ private:
|
|||||||
int m_tun2socksRetryCount = 0;
|
int m_tun2socksRetryCount = 0;
|
||||||
static constexpr int maxTun2SocksRetries = 5;
|
static constexpr int maxTun2SocksRetries = 5;
|
||||||
static constexpr int tun2socksRetryDelayMs = 400;
|
static constexpr int tun2socksRetryDelayMs = 400;
|
||||||
|
|
||||||
QString m_tunName;
|
|
||||||
Phase m_phase = Phase::Inactive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // XRAYPROTOCOL_H
|
#endif // XRAYPROTOCOL_H
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
#include "tunnel.h"
|
|
||||||
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "daemon/interfaceconfig.h"
|
|
||||||
|
|
||||||
Tunnel::Tunnel(QString ifname,
|
|
||||||
amnezia::DockerContainer container,
|
|
||||||
QJsonObject config,
|
|
||||||
QString remoteAddress,
|
|
||||||
QObject* parent)
|
|
||||||
: QObject(parent),
|
|
||||||
m_ifname(std::move(ifname)),
|
|
||||||
m_remoteAddress(std::move(remoteAddress)),
|
|
||||||
m_container(container),
|
|
||||||
m_config(std::move(config)) {}
|
|
||||||
|
|
||||||
Tunnel::~Tunnel() = default;
|
|
||||||
|
|
||||||
void Tunnel::prepare() {
|
|
||||||
if (m_state != State::Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(State::Preparing);
|
|
||||||
|
|
||||||
m_config.insert("ifname", m_ifname);
|
|
||||||
m_protocol.reset(VpnProtocol::factory(m_container, m_config));
|
|
||||||
if (!m_protocol) {
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(amnezia::ErrorCode::InternalError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(m_protocol.data(), &VpnProtocol::connectionStateChanged,
|
|
||||||
this, &Tunnel::onProtocolStateChanged);
|
|
||||||
connect(m_protocol.data(), &VpnProtocol::bytesChanged,
|
|
||||||
this, &Tunnel::bytesChanged);
|
|
||||||
connect(m_protocol.data(), &VpnProtocol::tunnelAddressesUpdated,
|
|
||||||
this, &Tunnel::addressesUpdated);
|
|
||||||
connect(m_protocol.data(), &VpnProtocol::primaryReady,
|
|
||||||
this, &Tunnel::onPrimaryReady);
|
|
||||||
connect(m_protocol.data(), &VpnProtocol::primaryFailed,
|
|
||||||
this, &Tunnel::onPrimaryFailed);
|
|
||||||
|
|
||||||
const amnezia::ErrorCode prepareErr = m_protocol->prepare();
|
|
||||||
if (prepareErr != amnezia::ErrorCode::NoError) {
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(prepareErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
|
|
||||||
|
|
||||||
const amnezia::ErrorCode err = m_protocol->start();
|
|
||||||
if (err != amnezia::ErrorCode::NoError) {
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::commit() {
|
|
||||||
if (m_state != State::Prepared) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(State::Committing);
|
|
||||||
startActivationDeadline(ACTIVATION_TIMEOUT_MSEC);
|
|
||||||
if (m_protocol) {
|
|
||||||
m_protocol->setPrimary(m_config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::onPrimaryReady() {
|
|
||||||
if (m_state != State::Committing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Active);
|
|
||||||
emit activated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::onPrimaryFailed() {
|
|
||||||
if (m_state != State::Committing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::deactivate() {
|
|
||||||
if (m_state == State::Gone || m_state == State::Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Gone);
|
|
||||||
if (m_protocol) {
|
|
||||||
m_protocol->stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::restart() {
|
|
||||||
deactivate();
|
|
||||||
setState(State::Idle);
|
|
||||||
prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::setState(State next) {
|
|
||||||
if (m_state == next) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_state = next;
|
|
||||||
emit stateChanged(m_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::startActivationDeadline(int msec) {
|
|
||||||
if (!m_deadline) {
|
|
||||||
m_deadline = new QTimer(this);
|
|
||||||
m_deadline->setSingleShot(true);
|
|
||||||
connect(m_deadline, &QTimer::timeout, this, [this]() {
|
|
||||||
if (m_state != State::Preparing && m_state != State::Committing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(amnezia::ErrorCode::InternalError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
m_deadline->start(msec);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::cancelActivationDeadline() {
|
|
||||||
if (m_deadline) {
|
|
||||||
m_deadline->stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tunnel::onProtocolStateChanged(Vpn::ConnectionState state) {
|
|
||||||
if (m_state == State::Preparing && state == Vpn::ConnectionState::Connected) {
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Prepared);
|
|
||||||
emit prepared();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool inLiveState = m_state == State::Preparing
|
|
||||||
|| m_state == State::Prepared
|
|
||||||
|| m_state == State::Committing
|
|
||||||
|| m_state == State::Active;
|
|
||||||
const bool isFailureSignal = state == Vpn::ConnectionState::Disconnected
|
|
||||||
|| state == Vpn::ConnectionState::Error;
|
|
||||||
if (inLiveState && isFailureSignal) {
|
|
||||||
cancelActivationDeadline();
|
|
||||||
setState(State::Failed);
|
|
||||||
emit failed(m_protocol ? m_protocol->lastError() : amnezia::ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -38,8 +38,6 @@ namespace amnezia
|
|||||||
XrayServerConfigInvalid = 215,
|
XrayServerConfigInvalid = 215,
|
||||||
XrayServerNoVlessClients = 216,
|
XrayServerNoVlessClients = 216,
|
||||||
XrayRealityKeysReadFailed = 217,
|
XrayRealityKeysReadFailed = 217,
|
||||||
ServerContainerRuntimeNotSupported = 218,
|
|
||||||
ContainerRuntimeServiceNotRunning = 219,
|
|
||||||
|
|
||||||
// Ssh connection errors
|
// Ssh connection errors
|
||||||
SshRequestDeniedError = 300,
|
SshRequestDeniedError = 300,
|
||||||
@@ -126,3 +124,5 @@ namespace amnezia
|
|||||||
Q_DECLARE_METATYPE(amnezia::ErrorCode)
|
Q_DECLARE_METATYPE(amnezia::ErrorCode)
|
||||||
|
|
||||||
#endif // ERRORCODES_H
|
#endif // ERRORCODES_H
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::XrayRealityKeysReadFailed):
|
case(ErrorCode::XrayRealityKeysReadFailed):
|
||||||
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
|
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
|
||||||
break;
|
break;
|
||||||
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
|
|
||||||
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
|
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
constexpr int BUFFER_SIZE = 8192;
|
constexpr int BUFFER_SIZE = 100;
|
||||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||||
int sock = -1, msgseq = 0;
|
int sock = -1, msgseq = 0;
|
||||||
struct nlmsghdr *nlh, *nlmsg;
|
struct nlmsghdr *nlh, *nlmsg;
|
||||||
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
// This struct contain route attributes (route type)
|
// This struct contain route attributes (route type)
|
||||||
struct rtattr *route_attribute;
|
struct rtattr *route_attribute;
|
||||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||||
char msgbuf[100], buffer[BUFFER_SIZE];
|
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||||
char *ptr = buffer;
|
char *ptr = buffer;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
nlh = (struct nlmsghdr *) ptr;
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
|
|
||||||
/* Check if the header is valid */
|
/* Check if the header is valid */
|
||||||
if((NLMSG_OK(nlh, received_bytes) == 0) ||
|
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||||
(nlh->nlmsg_type == NLMSG_ERROR))
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
{
|
{
|
||||||
perror("Error in received packet");
|
perror("Error in received packet");
|
||||||
return {};
|
return {};
|
||||||
@@ -355,15 +355,13 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Break if its not a multi part message */
|
/* Break if its not a multi part message */
|
||||||
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
|
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
|
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||||
|
|
||||||
/* parse response */
|
/* parse response */
|
||||||
int remaining = msg_len + received_bytes;
|
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||||
nlh = (struct nlmsghdr *) buffer;
|
|
||||||
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
|
|
||||||
{
|
{
|
||||||
/* Get the route data */
|
/* Get the route data */
|
||||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||||
@@ -372,10 +370,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Reset per-route to avoid cross-route state pollution */
|
|
||||||
memset(gateway_address, 0, sizeof(gateway_address));
|
|
||||||
memset(interface, 0, sizeof(interface));
|
|
||||||
|
|
||||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||||
|
|
||||||
@@ -397,15 +391,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((*gateway_address) && (*interface)) {
|
if ((*gateway_address) && (*interface)) {
|
||||||
if (QString::fromUtf8(interface).startsWith("amn")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(*gateway_address) || !(*interface))
|
|
||||||
qDebug() << "getGatewayAndIface: no gateway found";
|
|
||||||
close(sock);
|
close(sock);
|
||||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||||
#endif
|
#endif
|
||||||
@@ -460,15 +449,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||||
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||||
{
|
{
|
||||||
char ifname[IF_NAMESIZE] = {0};
|
|
||||||
const bool isTun = if_indextoname(rt->rtm_index, ifname)
|
|
||||||
&& QString::fromUtf8(ifname).startsWith("utun");
|
|
||||||
|
|
||||||
if (afinet_type[ip_type] == AF_INET)
|
if (afinet_type[ip_type] == AF_INET)
|
||||||
{
|
{
|
||||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
{
|
{
|
||||||
if (isTun) continue;
|
|
||||||
char dstStr4[INET_ADDRSTRLEN];
|
char dstStr4[INET_ADDRSTRLEN];
|
||||||
char srcStr4[INET_ADDRSTRLEN];
|
char srcStr4[INET_ADDRSTRLEN];
|
||||||
memcpy(srcStr4,
|
memcpy(srcStr4,
|
||||||
@@ -486,7 +470,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
{
|
{
|
||||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
{
|
{
|
||||||
if (isTun) continue;
|
|
||||||
char dstStr6[INET6_ADDRSTRLEN];
|
char dstStr6[INET6_ADDRSTRLEN];
|
||||||
char srcStr6[INET6_ADDRSTRLEN];
|
char srcStr6[INET6_ADDRSTRLEN];
|
||||||
memcpy(srcStr6,
|
memcpy(srcStr6,
|
||||||
|
|||||||
@@ -1,584 +0,0 @@
|
|||||||
#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 bool engineNamedInterface = tunnel
|
|
||||||
&& (VpnProtocol::isWireGuardBased(tunnel->container())
|
|
||||||
|| VpnProtocol::isXrayBased(tunnel->container()));
|
|
||||||
const QString ifname = engineNamedInterface ? updatedConfig.value("ifname").toString() : QString();
|
|
||||||
if (!ifname.isEmpty()) {
|
|
||||||
updatedConfig.insert("vpnGateway", gateway);
|
|
||||||
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
|
|
||||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
|
||||||
iface->enableKillSwitch(updatedConfig, 0);
|
|
||||||
}
|
|
||||||
iface->enablePeerTraffic(updatedConfig);
|
|
||||||
} else {
|
|
||||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
|
||||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
|
||||||
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
|
||||||
{
|
|
||||||
if (localAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
|
||||||
updatedConfig.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
|
||||||
updatedConfig.insert("vpnGateway", gateway);
|
|
||||||
updatedConfig.insert("vpnServer", NetworkUtilities::getIPAddress(updatedConfig.value(configKey::hostName).toString()));
|
|
||||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
|
||||||
iface->enableKillSwitch(updatedConfig, netInterfaces.at(i).index());
|
|
||||||
}
|
|
||||||
iface->enablePeerTraffic(updatedConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
|
||||||
if (QVariant(updatedConfig.value(configKey::killSwitchOption).toString()).toBool()) {
|
|
||||||
updatedConfig.insert("vpnServer",
|
|
||||||
NetworkUtilities::getIPAddress(updatedConfig.value(amnezia::configKey::hostName).toString()));
|
|
||||||
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(updatedConfig, 0);
|
|
||||||
//TODO: why it takes so long?
|
|
||||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
|
||||||
qWarning() << "VpnTrafficGuard::applyKillSwitch: Failed to enable killswitch";
|
|
||||||
} else {
|
|
||||||
qDebug() << "VpnTrafficGuard::applyKillSwitch: Successfully enabled killswitch";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
const QString proto = updatedConfig.value(configKey::vpnProto).toString();
|
|
||||||
const bool isXrayBased = (proto == ProtocolUtils::protoToString(Proto::Xray) ||
|
|
||||||
proto == ProtocolUtils::protoToString(Proto::SSXray));
|
|
||||||
if (isXrayBased) {
|
|
||||||
if (updatedConfig.value(configKey::splitTunnelType).toInt() == amnezia::route_mode_ns::VpnAllSites) {
|
|
||||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
|
||||||
const QString xrayIfname = tunnel ? 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
|
|
||||||
const bool engineNamedInterface = VpnProtocol::isWireGuardBased(tunnel->container())
|
|
||||||
|| VpnProtocol::isXrayBased(tunnel->container());
|
|
||||||
allowEndpoint(tunnel->remoteAddress(), engineNamedInterface ? tunnel->ifname() : QString());
|
|
||||||
#else
|
|
||||||
Q_UNUSED(tunnel)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnTrafficGuard::release(Tunnel* tunnel)
|
|
||||||
{
|
|
||||||
if (!tunnel) return;
|
|
||||||
disconnect(tunnel, nullptr, this, nullptr);
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
const bool engineNamedInterface = VpnProtocol::isWireGuardBased(tunnel->container())
|
|
||||||
|| VpnProtocol::isXrayBased(tunnel->container());
|
|
||||||
m_allowedEndpoints.removeAll(tunnel->remoteAddress());
|
|
||||||
IpcClient::withInterface([this, &tunnel, engineNamedInterface](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
iface->disableKillSwitchForTunnel(engineNamedInterface ? tunnel->ifname() : QString(), m_allowedEndpoints);
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
Q_UNUSED(tunnel)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnTrafficGuard::applyPolicy(Tunnel* tunnel)
|
|
||||||
{
|
|
||||||
if (!tunnel) return;
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
const QString ifname = tunnel->ifname();
|
|
||||||
|
|
||||||
if (VpnProtocol::isXrayBased(tunnel->container())) {
|
|
||||||
const QJsonObject cfg = tunnel->config();
|
|
||||||
const QString primary = cfg.value(amnezia::configKey::dns1).toString();
|
|
||||||
const QString secondary = cfg.value(amnezia::configKey::dns2).toString();
|
|
||||||
QList<QHostAddress> dns;
|
|
||||||
if (!primary.isEmpty()) dns.append(QHostAddress(primary));
|
|
||||||
if (!secondary.isEmpty() && secondary != primary) dns.append(QHostAddress(secondary));
|
|
||||||
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
auto updateRes = iface->updateResolvers(ifname, dns);
|
|
||||||
if (!updateRes.waitForFinished() || !updateRes.returnValue()) {
|
|
||||||
qWarning() << "VpnTrafficGuard::applyPolicy: updateResolvers failed for" << ifname;
|
|
||||||
}
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
const auto gw = NetworkUtilities::getGatewayAndIface();
|
|
||||||
const QString uplinkIface = gw.second.name();
|
|
||||||
const QString uplinkGateway = gw.first;
|
|
||||||
if (!uplinkIface.isEmpty() && !uplinkGateway.isEmpty()) {
|
|
||||||
auto add = iface->xrayAddUplinkRoutes(uplinkIface, uplinkGateway);
|
|
||||||
if (!add.waitForFinished() || !add.returnValue()) {
|
|
||||||
qWarning() << "VpnTrafficGuard::applyPolicy: xrayAddUplinkRoutes failed on" << uplinkIface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!VpnProtocol::isWireGuardBased(tunnel->container())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname());
|
|
||||||
const QStringList prefixes = allowedIpPrefixesFor(activate);
|
|
||||||
const QStringList excluded = excludedAddressesFor(activate);
|
|
||||||
const QStringList dns = resolversFor(activate);
|
|
||||||
const QString peer = tunnel->remoteAddress();
|
|
||||||
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
if (!peer.isEmpty()) iface->addExclusionRoute(ifname, peer);
|
|
||||||
for (const QString& addr : excluded) {
|
|
||||||
iface->addExclusionRoute(ifname, addr);
|
|
||||||
}
|
|
||||||
for (const QString& prefix : prefixes) {
|
|
||||||
iface->addAllowedIp(ifname, prefix);
|
|
||||||
}
|
|
||||||
iface->setTunnelResolvers(ifname, dns);
|
|
||||||
iface->flushDns();
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
Q_UNUSED(tunnel)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnTrafficGuard::revokePolicy(Tunnel* tunnel)
|
|
||||||
{
|
|
||||||
if (!tunnel) return;
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
const QString ifname = tunnel->ifname();
|
|
||||||
|
|
||||||
if (VpnProtocol::isXrayBased(tunnel->container())) {
|
|
||||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
iface->restoreResolvers();
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
const auto gw = NetworkUtilities::getGatewayAndIface();
|
|
||||||
const QString uplinkIface = gw.second.name();
|
|
||||||
const QString uplinkGateway = gw.first;
|
|
||||||
if (!uplinkIface.isEmpty()) {
|
|
||||||
iface->xrayRemoveUplinkRoutes(uplinkIface, uplinkGateway);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!VpnProtocol::isWireGuardBased(tunnel->container())) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#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
|
|
||||||
+218
-180
@@ -34,8 +34,8 @@ Daemon::Daemon(QObject* parent) : QObject(parent) {
|
|||||||
Q_ASSERT(s_daemon == nullptr);
|
Q_ASSERT(s_daemon == nullptr);
|
||||||
s_daemon = this;
|
s_daemon = this;
|
||||||
|
|
||||||
m_activationTimer.setSingleShot(false);
|
m_handshakeTimer.setSingleShot(true);
|
||||||
connect(&m_activationTimer, &QTimer::timeout, this, &Daemon::checkActivations);
|
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake);
|
||||||
}
|
}
|
||||||
|
|
||||||
Daemon::~Daemon() {
|
Daemon::~Daemon() {
|
||||||
@@ -43,9 +43,6 @@ Daemon::~Daemon() {
|
|||||||
|
|
||||||
logger.debug() << "Daemon released";
|
logger.debug() << "Daemon released";
|
||||||
|
|
||||||
qDeleteAll(m_tunnels);
|
|
||||||
m_tunnels.clear();
|
|
||||||
|
|
||||||
Q_ASSERT(s_daemon == this);
|
Q_ASSERT(s_daemon == this);
|
||||||
s_daemon = nullptr;
|
s_daemon = nullptr;
|
||||||
}
|
}
|
||||||
@@ -56,38 +53,69 @@ Daemon* Daemon::instance() {
|
|||||||
return s_daemon;
|
return s_daemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
|
bool Daemon::activate(const InterfaceConfig& config) {
|
||||||
logger.debug() << "Activating tunnel";
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
|
|
||||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
// There are 3 possible scenarios in which this method is called:
|
||||||
if (!wg) {
|
//
|
||||||
wg = createWgUtils();
|
// 1. the VPN is off: the method tries to enable the VPN.
|
||||||
if (!wg) {
|
// 2. the VPN is on and the platform doesn't support the server-switching:
|
||||||
logger.error() << "Failed to create wireguard utils.";
|
// 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;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_tunnels.insert(ifname, wg);
|
|
||||||
}
|
|
||||||
if (m_primaryIfname.isEmpty()) {
|
|
||||||
m_primaryIfname = ifname;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionState& cs = m_connections[ifname];
|
logger.warning() << "Already connected. Server switching not supported.";
|
||||||
cs.m_config = config;
|
if (!deactivate(false)) {
|
||||||
cs.m_date = QDateTime();
|
return false;
|
||||||
cs.m_deadline = QDateTime::currentDateTime().addMSecs(ACTIVATION_TIMEOUT_MSEC);
|
}
|
||||||
|
|
||||||
auto failure_guard = qScopeGuard([this, ifname] {
|
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||||
deactivateTunnel(ifname);
|
if (activate(config)) {
|
||||||
});
|
emit_failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
prepareActivation(config);
|
prepareActivation(config);
|
||||||
|
|
||||||
// Bring up the wireguard interface if not already done.
|
// Bring up the wireguard interface if not already done.
|
||||||
if (!wg->interfaceExists()) {
|
if (!wgutils()->interfaceExists()) {
|
||||||
InterfaceConfig bringupConfig = config;
|
// Create the interface.
|
||||||
bringupConfig.m_deferAddressSetup = (m_primaryIfname != ifname);
|
if (!wgutils()->addInterface(config)) {
|
||||||
if (!wg->addInterface(bringupConfig)) {
|
|
||||||
logger.error() << "Interface creation failed.";
|
logger.error() << "Interface creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -103,71 +131,60 @@ bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure routing for excluded addresses.
|
||||||
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
|
addExclusionRoute(IPAddress(i));
|
||||||
|
}
|
||||||
|
|
||||||
// Add the peer to this interface.
|
// Add the peer to this interface.
|
||||||
if (!wg->updatePeer(config)) {
|
if (!wgutils()->updatePeer(config)) {
|
||||||
logger.error() << "Peer creation failed.";
|
logger.error() << "Peer creation failed.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_activationTimer.isActive()) {
|
if (!maybeUpdateResolvers(config)) {
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connections[ifname].m_config = config;
|
// set routing
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
failure_guard.dismiss();
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
return true;
|
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||||
}
|
return false;
|
||||||
|
|
||||||
bool Daemon::deactivateTunnel(const QString& ifname) {
|
|
||||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
|
||||||
const ConnectionState cs = m_connections.value(ifname);
|
|
||||||
const InterfaceConfig& config = cs.m_config;
|
|
||||||
const bool wasPrimary = (ifname == m_primaryIfname);
|
|
||||||
const bool isLastTunnel = wg && m_tunnels.size() == 1;
|
|
||||||
|
|
||||||
if (wg) {
|
|
||||||
logger.debug() << "deactivateTunnel" << wg->interfaceName();
|
|
||||||
if (isLastTunnel) {
|
|
||||||
for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
|
|
||||||
wg->deleteExclusionRoute(prefix);
|
|
||||||
}
|
|
||||||
m_excludedAddrSet.clear();
|
|
||||||
}
|
}
|
||||||
wg->deletePeer(config);
|
|
||||||
wg->deleteInterface();
|
|
||||||
m_tunnels.remove(ifname);
|
|
||||||
delete wg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_connections.remove(ifname);
|
bool status = run(Up, config);
|
||||||
if (wasPrimary) {
|
logger.debug() << "Connection status:" << status;
|
||||||
m_primaryIfname.clear();
|
if (status) {
|
||||||
|
m_connections[config.m_hopType] = ConnectionState(config);
|
||||||
|
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||||
|
emit_failure_guard.dismiss();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the DNS is not the Gateway, it's a user defined DNS
|
||||||
|
// thus, not add any other :)
|
||||||
|
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||||
|
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,60 +209,26 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::addExclusionRoute(const QString &ifname, const QString &addr) {
|
bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
||||||
IPAddress prefix(addr);
|
|
||||||
if (m_excludedAddrSet.contains(prefix)) {
|
if (m_excludedAddrSet.contains(prefix)) {
|
||||||
m_excludedAddrSet[prefix]++;
|
m_excludedAddrSet[prefix]++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
WireguardUtils* wg = wgutilsFor(ifname);
|
if (!wgutils()->addExclusionRoute(prefix)) {
|
||||||
if (!wg) wg = primaryWgutils();
|
|
||||||
if (!wg || !wg->addExclusionRoute(prefix)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_excludedAddrSet[prefix] = 1;
|
m_excludedAddrSet[prefix] = 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::delExclusionRoute(const QString &ifname, const QString &addr) {
|
bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
||||||
IPAddress prefix(addr);
|
Q_ASSERT(m_excludedAddrSet.contains(prefix));
|
||||||
if (!m_excludedAddrSet.contains(prefix)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_excludedAddrSet[prefix] > 1) {
|
if (m_excludedAddrSet[prefix] > 1) {
|
||||||
m_excludedAddrSet[prefix]--;
|
m_excludedAddrSet[prefix]--;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
m_excludedAddrSet.remove(prefix);
|
m_excludedAddrSet.remove(prefix);
|
||||||
WireguardUtils* wg = wgutilsFor(ifname);
|
return wgutils()->deleteExclusionRoute(prefix);
|
||||||
if (!wg) wg = primaryWgutils();
|
|
||||||
return wg && wg->deleteExclusionRoute(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Daemon::addAllowedIp(const QString &ifname, const QString &prefix) {
|
|
||||||
WireguardUtils* wg = wgutilsFor(ifname);
|
|
||||||
return wg && wg->updateRoutePrefix(IPAddress(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Daemon::delAllowedIp(const QString &ifname, const QString &prefix) {
|
|
||||||
WireguardUtils* wg = wgutilsFor(ifname);
|
|
||||||
return wg && wg->deleteRoutePrefix(IPAddress(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Daemon::setTunnelResolvers(const QString &ifname, const QStringList &resolvers) {
|
|
||||||
WireguardUtils* wg = wgutilsFor(ifname);
|
|
||||||
if (!wg || !dnsutils()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QList<QHostAddress> hostAddrs;
|
|
||||||
for (const QString& r : resolvers) {
|
|
||||||
hostAddrs.append(QHostAddress(r));
|
|
||||||
}
|
|
||||||
return dnsutils()->updateResolvers(wg->interfaceName(), hostAddrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Daemon::restoreTunnelResolvers() {
|
|
||||||
return dnsutils() && dnsutils()->restoreResolvers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -457,16 +440,17 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
if (!obj.value("I5").isNull()) {
|
if (!obj.value("I5").isNull()) {
|
||||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||||
}
|
}
|
||||||
config.m_ifname = obj.value("ifname").toString();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Daemon::deactivate(bool emitSignals) {
|
bool Daemon::deactivate(bool emitSignals) {
|
||||||
const QString primary = m_primaryIfname;
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
|
|
||||||
if (m_connections.contains(primary)) {
|
// Deactivate the main interface.
|
||||||
if (!run(Down, m_connections.value(primary).m_config)) {
|
if (!m_connections.isEmpty()) {
|
||||||
|
const ConnectionState& state = m_connections.first();
|
||||||
|
if (!run(Down, state.m_config)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,26 +459,31 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||||||
emit disconnected();
|
emit disconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList ifnames = m_tunnels.keys();
|
// Cleanup DNS
|
||||||
for (const QString& ifname : ifnames) {
|
if (!dnsutils()->restoreResolvers()) {
|
||||||
if (ifname != primary) {
|
logger.warning() << "Failed to restore DNS resolvers.";
|
||||||
deactivateTunnel(ifname);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* wg = primaryWgutils()) {
|
// Cleanup peers and routing
|
||||||
for (const IPAddress& prefix : m_excludedAddrSet.keys()) {
|
for (const ConnectionState& state : m_connections) {
|
||||||
wg->deleteExclusionRoute(prefix);
|
const InterfaceConfig& config = state.m_config;
|
||||||
|
logger.debug() << "Deleting routes for" << config.m_hopType;
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
wgutils()->deleteRoutePrefix(ip);
|
||||||
}
|
}
|
||||||
|
wgutils()->deletePeer(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup routing for excluded addresses.
|
||||||
|
for (auto iterator = m_excludedAddrSet.constBegin();
|
||||||
|
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
|
||||||
|
wgutils()->deleteExclusionRoute(iterator.key());
|
||||||
}
|
}
|
||||||
m_excludedAddrSet.clear();
|
m_excludedAddrSet.clear();
|
||||||
|
|
||||||
if (m_tunnels.contains(primary)) {
|
m_connections.clear();
|
||||||
deactivateTunnel(primary);
|
// Delete the interface
|
||||||
}
|
return wgutils()->deleteInterface();
|
||||||
|
|
||||||
m_activationTimer.stop();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Daemon::logs() {
|
QString Daemon::logs() {
|
||||||
@@ -503,18 +492,79 @@ QString Daemon::logs() {
|
|||||||
|
|
||||||
void Daemon::cleanLogs() { }
|
void Daemon::cleanLogs() { }
|
||||||
|
|
||||||
|
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||||
|
if (!m_connections.contains(config.m_hopType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const InterfaceConfig& current =
|
||||||
|
m_connections.value(config.m_hopType).m_config;
|
||||||
|
|
||||||
|
return current.m_privateKey == config.m_privateKey &&
|
||||||
|
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||||
|
current.m_deviceIpv6Address == config.m_deviceIpv6Address &&
|
||||||
|
current.m_serverIpv4Gateway == config.m_serverIpv4Gateway &&
|
||||||
|
current.m_serverIpv6Gateway == config.m_serverIpv6Gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||||
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
|
|
||||||
|
logger.debug() << "Switching server for" << config.m_hopType;
|
||||||
|
|
||||||
|
Q_ASSERT(m_connections.contains(config.m_hopType));
|
||||||
|
const InterfaceConfig& lastConfig =
|
||||||
|
m_connections.value(config.m_hopType).m_config;
|
||||||
|
|
||||||
|
// Configure routing for new excluded addresses.
|
||||||
|
for (const QString& i : config.m_excludedAddresses) {
|
||||||
|
addExclusionRoute(IPAddress(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate the new peer and its routes.
|
||||||
|
if (!wgutils()->updatePeer(config)) {
|
||||||
|
logger.error() << "Server switch failed to update the wireguard interface";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
|
logger.error() << "Server switch failed to update the routing table";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove routing entries for the old peer.
|
||||||
|
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||||
|
delExclusionRoute(QHostAddress(i));
|
||||||
|
}
|
||||||
|
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||||
|
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||||
|
wgutils()->deleteRoutePrefix(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the old peer if it is no longer necessary.
|
||||||
|
if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) {
|
||||||
|
if (!wgutils()->deletePeer(lastConfig)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections[config.m_hopType] = ConnectionState(config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject Daemon::getStatus() {
|
QJsonObject Daemon::getStatus() {
|
||||||
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
logger.debug() << "Status request";
|
logger.debug() << "Status request";
|
||||||
|
|
||||||
WireguardUtils* wg = primaryWgutils();
|
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||||
if (!wg || !wg->interfaceExists() || !m_connections.contains(m_primaryIfname)) {
|
|
||||||
json.insert("connected", QJsonValue(false));
|
json.insert("connected", QJsonValue(false));
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionState& connection = m_connections.value(m_primaryIfname);
|
const ConnectionState& connection = m_connections.first();
|
||||||
QList<WireguardUtils::PeerStatus> peers = wg->getPeerStatus();
|
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||||
continue;
|
continue;
|
||||||
@@ -534,50 +584,38 @@ QJsonObject Daemon::getStatus() {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Daemon::checkActivations() {
|
void Daemon::checkHandshake() {
|
||||||
const QDateTime now = QDateTime::currentDateTime();
|
Q_ASSERT(wgutils() != nullptr);
|
||||||
QStringList timedOut;
|
|
||||||
bool anyPending = false;
|
|
||||||
|
|
||||||
for (auto it = m_connections.begin(); it != m_connections.end(); ++it) {
|
logger.debug() << "Checking for handshake...";
|
||||||
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;
|
|
||||||
|
|
||||||
WireguardUtils* wg = m_tunnels.value(ifname);
|
int pendingHandshakes = 0;
|
||||||
bool handshaked = false;
|
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||||
if (wg) {
|
for (ConnectionState& connection : m_connections) {
|
||||||
for (const WireguardUtils::PeerStatus& status : wg->getPeerStatus()) {
|
const InterfaceConfig& config = connection.m_config;
|
||||||
if (status.m_pubkey != cs.m_config.m_serverPublicKey) {
|
if (connection.m_date.isValid()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (status.m_handshake != 0) {
|
|
||||||
cs.m_date.setMSecsSinceEpoch(status.m_handshake);
|
|
||||||
emit tunnelConnected(ifname, status.m_pubkey);
|
|
||||||
handshaked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (handshaked) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (cs.m_deadline.isValid() && now > cs.m_deadline) {
|
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||||
timedOut.append(ifname);
|
|
||||||
} else {
|
// Check if the handshake has completed.
|
||||||
anyPending = true;
|
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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString& ifname : timedOut) {
|
// Check again if there were connections that haven't completed a handshake.
|
||||||
logger.warning() << "Tunnel handshake timed out:" << m_tunnels.value(ifname)->interfaceName();
|
if (pendingHandshakes > 0) {
|
||||||
emit tunnelHandshakeFailed(ifname);
|
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||||
deactivateTunnel(ifname);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!anyPending) {
|
|
||||||
m_activationTimer.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-25
@@ -22,6 +22,7 @@ class Daemon : public QObject {
|
|||||||
enum Op {
|
enum Op {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
|
Switch,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Daemon(QObject* parent);
|
explicit Daemon(QObject* parent);
|
||||||
@@ -31,22 +32,10 @@ class Daemon : public QObject {
|
|||||||
|
|
||||||
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
||||||
|
|
||||||
bool activate(const QString& ifname, const InterfaceConfig& config);
|
virtual bool activate(const InterfaceConfig& config);
|
||||||
bool setPrimary(const QString& ifname, const InterfaceConfig& config);
|
|
||||||
bool deactivateTunnel(const QString& ifname);
|
|
||||||
virtual bool deactivate(bool emitSignals = true);
|
virtual bool deactivate(bool emitSignals = true);
|
||||||
virtual QJsonObject getStatus();
|
virtual QJsonObject getStatus();
|
||||||
|
|
||||||
bool addExclusionRoute(const QString &ifname, const QString &addr);
|
|
||||||
bool delExclusionRoute(const QString &ifname, const QString &addr);
|
|
||||||
bool addAllowedIp(const QString &ifname, const QString &prefix);
|
|
||||||
bool delAllowedIp(const QString &ifname, const QString &prefix);
|
|
||||||
bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers);
|
|
||||||
bool restoreTunnelResolvers();
|
|
||||||
|
|
||||||
const QString& primaryIfname() const { return m_primaryIfname; }
|
|
||||||
WireguardUtils* wgutilsFor(const QString& ifname) const { return m_tunnels.value(ifname); }
|
|
||||||
|
|
||||||
// Callback before any Activating measure is done
|
// Callback before any Activating measure is done
|
||||||
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
||||||
Q_UNUSED(config) };
|
Q_UNUSED(config) };
|
||||||
@@ -57,15 +46,19 @@ class Daemon : public QObject {
|
|||||||
void cleanLogs();
|
void cleanLogs();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tunnelConnected(const QString& ifname, const QString& pubkey);
|
void connected(const QString& pubkey);
|
||||||
void tunnelHandshakeFailed(const QString& ifname);
|
/**
|
||||||
|
* Can be fired if a call to activate() was unsucessfull
|
||||||
|
* and connected systems should rollback
|
||||||
|
*/
|
||||||
|
void activationFailure();
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkActivations();
|
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||||
WireguardUtils* primaryWgutils() const { return m_tunnels.value(m_primaryIfname); }
|
bool addExclusionRoute(const IPAddress& address);
|
||||||
QTimer m_activationTimer;
|
bool delExclusionRoute(const IPAddress& address);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||||
@@ -73,11 +66,9 @@ class Daemon : public QObject {
|
|||||||
Q_UNUSED(config);
|
Q_UNUSED(config);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
virtual WireguardUtils* createWgUtils() = 0;
|
virtual bool supportServerSwitching(const InterfaceConfig& config) const;
|
||||||
|
virtual bool switchServer(const InterfaceConfig& config);
|
||||||
QMap<QString, WireguardUtils*> m_tunnels;
|
virtual WireguardUtils* wgutils() const = 0;
|
||||||
QString m_primaryIfname;
|
|
||||||
|
|
||||||
virtual bool supportIPUtils() const { return false; }
|
virtual bool supportIPUtils() const { return false; }
|
||||||
virtual IPUtils* iputils() { return nullptr; }
|
virtual IPUtils* iputils() { return nullptr; }
|
||||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||||
@@ -85,16 +76,18 @@ class Daemon : public QObject {
|
|||||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||||
QStringList& list);
|
QStringList& list);
|
||||||
|
|
||||||
|
void checkHandshake();
|
||||||
|
|
||||||
class ConnectionState {
|
class ConnectionState {
|
||||||
public:
|
public:
|
||||||
ConnectionState(){};
|
ConnectionState(){};
|
||||||
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
||||||
QDateTime m_date;
|
QDateTime m_date;
|
||||||
QDateTime m_deadline;
|
|
||||||
InterfaceConfig m_config;
|
InterfaceConfig m_config;
|
||||||
};
|
};
|
||||||
QMap<QString, ConnectionState> m_connections;
|
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||||
QHash<IPAddress, int> m_excludedAddrSet;
|
QHash<IPAddress, int> m_excludedAddrSet;
|
||||||
|
QTimer m_handshakeTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DAEMON_H
|
#endif // DAEMON_H
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
|
|||||||
&DaemonLocalServerConnection::readData);
|
&DaemonLocalServerConnection::readData);
|
||||||
|
|
||||||
Daemon* daemon = Daemon::instance();
|
Daemon* daemon = Daemon::instance();
|
||||||
connect(daemon, &Daemon::tunnelConnected,
|
connect(daemon, &Daemon::connected, this,
|
||||||
this, &DaemonLocalServerConnection::onTunnelConnected);
|
&DaemonLocalServerConnection::connected);
|
||||||
connect(daemon, &Daemon::tunnelHandshakeFailed,
|
|
||||||
this, &DaemonLocalServerConnection::onTunnelHandshakeFailed);
|
|
||||||
connect(daemon, &Daemon::disconnected, this,
|
connect(daemon, &Daemon::disconnected, this,
|
||||||
&DaemonLocalServerConnection::disconnected);
|
&DaemonLocalServerConnection::disconnected);
|
||||||
connect(daemon, &Daemon::backendFailure, this,
|
connect(daemon, &Daemon::backendFailure, this,
|
||||||
@@ -109,44 +107,19 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
|||||||
InterfaceConfig config;
|
InterfaceConfig config;
|
||||||
if (!Daemon::parseConfig(obj, config)) {
|
if (!Daemon::parseConfig(obj, config)) {
|
||||||
logger.error() << "Invalid configuration";
|
logger.error() << "Invalid configuration";
|
||||||
disconnected();
|
emit disconnected();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Daemon::instance()->activate(config.m_ifname, config)) {
|
|
||||||
|
if (!Daemon::instance()->activate(config)) {
|
||||||
logger.error() << "Failed to activate the interface";
|
logger.error() << "Failed to activate the interface";
|
||||||
disconnected();
|
emit disconnected();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "deactivate") {
|
if (type == "deactivate") {
|
||||||
const QString ifname = obj.value("ifname").toString();
|
Daemon::instance()->deactivate(true);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,19 +146,10 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
|||||||
logger.warning() << "Invalid command:" << type;
|
logger.warning() << "Invalid command:" << type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DaemonLocalServerConnection::onTunnelConnected(const QString& ifname,
|
void DaemonLocalServerConnection::connected(const QString& pubkey) {
|
||||||
const QString& pubkey) {
|
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj.insert("type", "connected");
|
obj.insert("type", "connected");
|
||||||
obj.insert("ifname", ifname);
|
obj.insert("pubkey", QJsonValue(pubkey));
|
||||||
obj.insert("pubkey", pubkey);
|
|
||||||
write(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DaemonLocalServerConnection::onTunnelHandshakeFailed(const QString& ifname) {
|
|
||||||
QJsonObject obj;
|
|
||||||
obj.insert("type", "disconnected");
|
|
||||||
obj.insert("ifname", ifname);
|
|
||||||
write(obj);
|
write(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#define DAEMONLOCALSERVERCONNECTION_H
|
#define DAEMONLOCALSERVERCONNECTION_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "daemonerrors.h"
|
#include "daemonerrors.h"
|
||||||
|
|
||||||
@@ -24,8 +23,7 @@ class DaemonLocalServerConnection final : public QObject {
|
|||||||
|
|
||||||
void parseCommand(const QByteArray& json);
|
void parseCommand(const QByteArray& json);
|
||||||
|
|
||||||
void onTunnelConnected(const QString& ifname, const QString& pubkey);
|
void connected(const QString& pubkey);
|
||||||
void onTunnelHandshakeFailed(const QString& ifname);
|
|
||||||
void disconnected();
|
void disconnected();
|
||||||
void backendFailure(DaemonError err);
|
void backendFailure(DaemonError err);
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ QJsonObject InterfaceConfig::toJson() const {
|
|||||||
}
|
}
|
||||||
json.insert("vpnDisabledApps", disabledApps);
|
json.insert("vpnDisabledApps", disabledApps);
|
||||||
|
|
||||||
json.insert("ifname", m_ifname);
|
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
|
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
|
|
||||||
constexpr int ACTIVATION_TIMEOUT_MSEC = 30000;
|
|
||||||
|
|
||||||
class InterfaceConfig {
|
class InterfaceConfig {
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
|
|
||||||
@@ -59,8 +57,6 @@ class InterfaceConfig {
|
|||||||
QString m_underloadPacketMagicHeader;
|
QString m_underloadPacketMagicHeader;
|
||||||
QString m_transportPacketMagicHeader;
|
QString m_transportPacketMagicHeader;
|
||||||
QMap<QString, QString> m_specialJunk;
|
QMap<QString, QString> m_specialJunk;
|
||||||
QString m_ifname;
|
|
||||||
bool m_deferAddressSetup = false;
|
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
QString toWgConf(
|
QString toWgConf(
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#include "interfaceconfig.h"
|
#include "interfaceconfig.h"
|
||||||
|
|
||||||
|
constexpr const char* WG_INTERFACE = "amn0";
|
||||||
|
|
||||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||||
|
|
||||||
class WireguardUtils : public QObject {
|
class WireguardUtils : public QObject {
|
||||||
@@ -33,7 +35,7 @@ class WireguardUtils : public QObject {
|
|||||||
virtual ~WireguardUtils() = default;
|
virtual ~WireguardUtils() = default;
|
||||||
|
|
||||||
virtual bool interfaceExists() = 0;
|
virtual bool interfaceExists() = 0;
|
||||||
virtual QString interfaceName() = 0;
|
virtual QString interfaceName() { return WG_INTERFACE; }
|
||||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||||
virtual bool deleteInterface() = 0;
|
virtual bool deleteInterface() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <libssh/libssh.h>
|
#include <libssh/libssh.h>
|
||||||
|
|
||||||
#include "amneziaApplication.h"
|
#include "amneziaApplication.h"
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ class ControllerImpl : public QObject {
|
|||||||
// "disconnecting" state until the "disconnected" signal is received.
|
// "disconnecting" state until the "disconnected" signal is received.
|
||||||
virtual void deactivate() = 0;
|
virtual void deactivate() = 0;
|
||||||
|
|
||||||
virtual void setPrimary(const QJsonObject& config) { Q_UNUSED(config) }
|
|
||||||
|
|
||||||
// This method is used to retrieve the VPN tunnel status (mainly the number
|
// This method is used to retrieve the VPN tunnel status (mainly the number
|
||||||
// of bytes sent and received). It's called always when the VPN tunnel is
|
// of bytes sent and received). It's called always when the VPN tunnel is
|
||||||
// active.
|
// active.
|
||||||
@@ -73,13 +71,11 @@ class ControllerImpl : public QObject {
|
|||||||
void initialized(bool status, bool connected,
|
void initialized(bool status, bool connected,
|
||||||
const QDateTime& connectionDate);
|
const QDateTime& connectionDate);
|
||||||
|
|
||||||
|
// These 2 signals can be dispatched at any time.
|
||||||
void connected(const QString& pubkey,
|
void connected(const QString& pubkey,
|
||||||
const QDateTime& connectionTimestamp = QDateTime());
|
const QDateTime& connectionTimestamp = QDateTime());
|
||||||
void disconnected();
|
void disconnected();
|
||||||
|
|
||||||
void primaryReady();
|
|
||||||
void primaryFailed();
|
|
||||||
|
|
||||||
// This method should be emitted after a checkStatus() call.
|
// This method should be emitted after a checkStatus() call.
|
||||||
// "serverIpv4Gateway" is the current VPN tunnel gateway.
|
// "serverIpv4Gateway" is the current VPN tunnel gateway.
|
||||||
// "deviceIpv4Address" is the address of the VPN client.
|
// "deviceIpv4Address" is the address of the VPN client.
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ namespace {
|
|||||||
Logger logger("LocalSocketController");
|
Logger logger("LocalSocketController");
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalSocketController::LocalSocketController(const QString& ifname)
|
LocalSocketController::LocalSocketController() {
|
||||||
: m_ifname(ifname) {
|
|
||||||
MZ_COUNT_CTOR(LocalSocketController);
|
MZ_COUNT_CTOR(LocalSocketController);
|
||||||
|
|
||||||
m_socket = new QLocalSocket(this);
|
m_socket = new QLocalSocket(this);
|
||||||
@@ -122,8 +121,7 @@ void LocalSocketController::daemonConnected() {
|
|||||||
checkStatus();
|
checkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfig,
|
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
const QString& ifname) {
|
|
||||||
QString protocolName = rawConfig.value("protocol").toString();
|
QString protocolName = rawConfig.value("protocol").toString();
|
||||||
|
|
||||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||||
@@ -136,9 +134,11 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi
|
|||||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
|
json.insert("type", "activate");
|
||||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||||
json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey));
|
json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey));
|
||||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp));
|
json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp));
|
||||||
|
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
|
||||||
|
|
||||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||||
@@ -230,6 +230,7 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi
|
|||||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||||
|
|
||||||
QJsonArray jsExcludedAddresses;
|
QJsonArray jsExcludedAddresses;
|
||||||
|
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||||
if (splitTunnelType == 2) {
|
if (splitTunnelType == 2) {
|
||||||
for (auto v : splitTunnelSites) {
|
for (auto v : splitTunnelSites) {
|
||||||
QString ipRange = v.toString();
|
QString ipRange = v.toString();
|
||||||
@@ -291,23 +292,6 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi
|
|||||||
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
|
json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5));
|
||||||
}
|
}
|
||||||
|
|
||||||
json.insert("ifname", ifname);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalSocketController::activate(const QJsonObject& rawConfig) {
|
|
||||||
const QString protocolName = rawConfig.value("protocol").toString();
|
|
||||||
const QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
|
||||||
m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString();
|
|
||||||
|
|
||||||
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
|
|
||||||
json.insert("type", "activate");
|
|
||||||
write(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalSocketController::setPrimary(const QJsonObject& rawConfig) {
|
|
||||||
QJsonObject json = buildActivateJson(rawConfig, m_ifname);
|
|
||||||
json.insert("type", "setPrimary");
|
|
||||||
write(json);
|
write(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +306,6 @@ void LocalSocketController::deactivate() {
|
|||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
json.insert("type", "deactivate");
|
json.insert("type", "deactivate");
|
||||||
json.insert("ifname", m_ifname);
|
|
||||||
write(json);
|
write(json);
|
||||||
emit disconnected();
|
emit disconnected();
|
||||||
}
|
}
|
||||||
@@ -488,20 +471,12 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto belongsToThisTunnel = [this, &obj]() {
|
|
||||||
const QJsonValue val = obj.value("ifname");
|
|
||||||
return !val.isString() || val.toString() == m_ifname;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type == "disconnected") {
|
if (type == "disconnected") {
|
||||||
if (!belongsToThisTunnel()) return;
|
|
||||||
disconnectInternal();
|
disconnectInternal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "connected") {
|
if (type == "connected") {
|
||||||
if (!belongsToThisTunnel()) return;
|
|
||||||
|
|
||||||
QJsonValue pubkey = obj.value("pubkey");
|
QJsonValue pubkey = obj.value("pubkey");
|
||||||
if (!pubkey.isString()) {
|
if (!pubkey.isString()) {
|
||||||
logger.error() << "Unexpected pubkey value";
|
logger.error() << "Unexpected pubkey value";
|
||||||
@@ -519,18 +494,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == "primaryReady") {
|
|
||||||
if (!belongsToThisTunnel()) return;
|
|
||||||
emit primaryReady();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "primaryFailed") {
|
|
||||||
if (!belongsToThisTunnel()) return;
|
|
||||||
emit primaryFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "backendFailure") {
|
if (type == "backendFailure") {
|
||||||
if (!obj.contains("errorCode")) {
|
if (!obj.contains("errorCode")) {
|
||||||
// report a generic error if we dont know what it is.
|
// report a generic error if we dont know what it is.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class LocalSocketController final : public ControllerImpl {
|
|||||||
Q_DISABLE_COPY_MOVE(LocalSocketController)
|
Q_DISABLE_COPY_MOVE(LocalSocketController)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LocalSocketController(const QString& ifname);
|
LocalSocketController();
|
||||||
~LocalSocketController();
|
~LocalSocketController();
|
||||||
|
|
||||||
void initialize(const Device* device, const Keys* keys) override;
|
void initialize(const Device* device, const Keys* keys) override;
|
||||||
@@ -28,8 +28,6 @@ class LocalSocketController final : public ControllerImpl {
|
|||||||
|
|
||||||
void deactivate() override;
|
void deactivate() override;
|
||||||
|
|
||||||
void setPrimary(const QJsonObject& rawConfig) override;
|
|
||||||
|
|
||||||
void checkStatus() override;
|
void checkStatus() override;
|
||||||
|
|
||||||
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
|
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
|
||||||
@@ -38,10 +36,6 @@ class LocalSocketController final : public ControllerImpl {
|
|||||||
|
|
||||||
bool multihopSupported() override { return true; }
|
bool multihopSupported() override { return true; }
|
||||||
|
|
||||||
public:
|
|
||||||
static QJsonObject buildActivateJson(const QJsonObject& rawConfig,
|
|
||||||
const QString& ifname);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initializeInternal();
|
void initializeInternal();
|
||||||
void disconnectInternal();
|
void disconnectInternal();
|
||||||
@@ -65,7 +59,6 @@ class LocalSocketController final : public ControllerImpl {
|
|||||||
|
|
||||||
QByteArray m_buffer;
|
QByteArray m_buffer;
|
||||||
|
|
||||||
QString m_ifname;
|
|
||||||
QString m_deviceIpv4;
|
QString m_deviceIpv4;
|
||||||
std::function<void(const QString&)> m_logCallback = nullptr;
|
std::function<void(const QString&)> m_logCallback = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
|||||||
|
|
||||||
// Setup the interface to interact with
|
// Setup the interface to interact with
|
||||||
struct ifreq ifr;
|
struct ifreq ifr;
|
||||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
|
|
||||||
// MTU
|
// MTU
|
||||||
// FIXME: We need to know how many layers deep this particular
|
// FIXME: We need to know how many layers deep this particular
|
||||||
@@ -76,7 +76,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
||||||
|
|
||||||
// Name the interface and set family
|
// Name the interface and set family
|
||||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
ifr.ifr_addr.sa_family = AF_INET;
|
ifr.ifr_addr.sa_family = AF_INET;
|
||||||
|
|
||||||
// Get the device address to add to interface
|
// Get the device address to add to interface
|
||||||
@@ -126,7 +126,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
|||||||
|
|
||||||
// Get the index of named ifr and link with ifr6
|
// Get the index of named ifr and link with ifr6
|
||||||
struct ifreq ifr;
|
struct ifreq ifr;
|
||||||
strncpy(ifr.ifr_name, config.m_ifname.toUtf8().constData(), IFNAMSIZ);
|
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||||
ifr.ifr_addr.sa_family = AF_INET6;
|
ifr.ifr_addr.sa_family = AF_INET6;
|
||||||
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
|
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
|
|||||||
|
|
||||||
logger.debug() << "Daemon created";
|
logger.debug() << "Daemon created";
|
||||||
|
|
||||||
|
m_wgutils = new WireguardUtilsLinux(this);
|
||||||
m_dnsutils = new DnsUtilsLinux(this);
|
m_dnsutils = new DnsUtilsLinux(this);
|
||||||
m_iputils = new IPUtilsLinux(this);
|
m_iputils = new IPUtilsLinux(this);
|
||||||
|
|
||||||
@@ -49,4 +50,3 @@ LinuxDaemon* LinuxDaemon::instance() {
|
|||||||
Q_ASSERT(s_daemon);
|
Q_ASSERT(s_daemon);
|
||||||
return s_daemon;
|
return s_daemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
#include "wireguardutilslinux.h"
|
#include "wireguardutilslinux.h"
|
||||||
|
|
||||||
class LinuxDaemon final : public Daemon {
|
class LinuxDaemon final : public Daemon {
|
||||||
|
friend class IPUtilsMacos;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LinuxDaemon();
|
LinuxDaemon();
|
||||||
~LinuxDaemon();
|
~LinuxDaemon();
|
||||||
@@ -19,15 +21,13 @@ class LinuxDaemon final : public Daemon {
|
|||||||
static LinuxDaemon* instance();
|
static LinuxDaemon* instance();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
|
|
||||||
WireguardUtils* createWgUtils() override {
|
|
||||||
return new WireguardUtilsLinux(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
WireguardUtilsLinux* m_wgutils = nullptr;
|
||||||
DnsUtilsLinux* m_dnsutils = nullptr;
|
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||||
IPUtilsLinux* m_iputils = nullptr;
|
IPUtilsLinux* m_iputils = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,8 +193,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
|
|||||||
QStringList result;
|
QStringList result;
|
||||||
for (const QString& server : servers)
|
for (const QString& server : servers)
|
||||||
{
|
{
|
||||||
result << QStringLiteral("-o amn+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o amn0+ -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 amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||||
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||||
@@ -278,7 +278,7 @@ void LinuxFirewall::install()
|
|||||||
});
|
});
|
||||||
|
|
||||||
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
||||||
QStringLiteral("-o amn+ -j ACCEPT"),
|
QStringLiteral("-o amn0+ -j ACCEPT"),
|
||||||
QStringLiteral("-o tun0+ -j ACCEPT"),
|
QStringLiteral("-o tun0+ -j ACCEPT"),
|
||||||
QStringLiteral("-o tun2+ -j ACCEPT"),
|
QStringLiteral("-o tun2+ -j ACCEPT"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,33 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
// Descriptor for a set of firewall rules to be appled.
|
||||||
|
//
|
||||||
|
struct FirewallParams
|
||||||
|
{
|
||||||
|
QStringList dnsServers;
|
||||||
|
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
||||||
|
QStringList allowAddrs;
|
||||||
|
QStringList blockAddrs;
|
||||||
|
// The follow flags indicate which general rulesets are needed. Note that
|
||||||
|
// this is after some sanity filtering, i.e. an allow rule may be listed
|
||||||
|
// as not needed if there were no block rules preceding it. The rulesets
|
||||||
|
// should be thought of as in last-match order.
|
||||||
|
|
||||||
|
bool blockAll; // Block all traffic by default
|
||||||
|
bool allowVPN; // Exempt traffic through VPN tunnel
|
||||||
|
bool allowDHCP; // Exempt DHCP traffic
|
||||||
|
bool blockIPv6; // Block all IPv6 traffic
|
||||||
|
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
||||||
|
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
||||||
|
bool allowPIA; // Exempt PIA executables
|
||||||
|
bool allowLoopback; // Exempt loopback traffic
|
||||||
|
bool allowHnsd; // Exempt Handshake DNS traffic
|
||||||
|
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
||||||
|
bool allowNets;
|
||||||
|
bool blockNets;
|
||||||
|
};
|
||||||
|
|
||||||
class LinuxFirewall
|
class LinuxFirewall
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ typedef struct wg_allowedip {
|
|||||||
struct wg_allowedip *next_allowedip;
|
struct wg_allowedip *next_allowedip;
|
||||||
} wg_allowedip;
|
} wg_allowedip;
|
||||||
|
|
||||||
|
constexpr const char* WG_INTERFACE = "amn0";
|
||||||
|
|
||||||
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
static void nlmsg_append_attr(struct nlmsghdr* nlmsg, size_t maxlen,
|
||||||
int attrtype, const void* attrdata,
|
int attrtype, const void* attrdata,
|
||||||
size_t attrlen);
|
size_t attrlen);
|
||||||
@@ -53,8 +55,6 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
|
|||||||
MZ_COUNT_CTOR(LinuxRouteMonitor);
|
MZ_COUNT_CTOR(LinuxRouteMonitor);
|
||||||
logger.debug() << "LinuxRouteMonitor created.";
|
logger.debug() << "LinuxRouteMonitor created.";
|
||||||
|
|
||||||
m_physicalGateway = NetworkUtilities::getGatewayAndIface().first;
|
|
||||||
|
|
||||||
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||||
if (m_nlsock < 0) {
|
if (m_nlsock < 0) {
|
||||||
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
||||||
@@ -63,7 +63,7 @@ LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
|
|||||||
struct sockaddr_nl nladdr;
|
struct sockaddr_nl nladdr;
|
||||||
memset(&nladdr, 0, sizeof(nladdr));
|
memset(&nladdr, 0, sizeof(nladdr));
|
||||||
nladdr.nl_family = AF_NETLINK;
|
nladdr.nl_family = AF_NETLINK;
|
||||||
nladdr.nl_pid = 0;
|
nladdr.nl_pid = getpid();
|
||||||
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
||||||
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rtm->rtm_type == RTN_UNICAST) {
|
if (rtm->rtm_type == RTN_UNICAST) {
|
||||||
int index = if_nametoindex(m_ifname.toUtf8().constData());
|
int index = if_nametoindex(WG_INTERFACE);
|
||||||
|
|
||||||
if (index <= 0) {
|
if (index <= 0) {
|
||||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||||
@@ -164,15 +164,14 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rtm->rtm_type == RTN_THROW) {
|
if (rtm->rtm_type == RTN_THROW) {
|
||||||
if (action == RTM_NEWROUTE) {
|
QString gateway = NetworkUtilities::getGatewayAndIface().first;
|
||||||
if (m_physicalGateway.isEmpty()) {
|
if (gateway.isEmpty()) {
|
||||||
logger.warning() << "No physical gateway available, skipping exclusion route";
|
logger.warning() << "No default gateway available, skipping exclusion route";
|
||||||
return false;
|
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);
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||||
rtm->rtm_type = RTN_UNICAST;
|
rtm->rtm_type = RTN_UNICAST;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class LinuxRouteMonitor final : public QObject {
|
|||||||
bool rtmSendRoute(int action, int flags, int type,
|
bool rtmSendRoute(int action, int flags, int type,
|
||||||
const IPAddress& prefix);
|
const IPAddress& prefix);
|
||||||
QString m_ifname;
|
QString m_ifname;
|
||||||
QString m_physicalGateway;
|
|
||||||
unsigned int m_ifindex = 0;
|
unsigned int m_ifindex = 0;
|
||||||
int m_nlsock = -1;
|
int m_nlsock = -1;
|
||||||
int m_nlseq = 0;
|
int m_nlseq = 0;
|
||||||
|
|||||||
@@ -8,15 +8,17 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
|
#include "linuxfirewall.h"
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include "killswitch.h"
|
||||||
|
|
||||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||||
|
|
||||||
@@ -57,20 +59,19 @@ void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||||
|
Q_UNUSED(config);
|
||||||
if (m_tunnel.state() != QProcess::NotRunning) {
|
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||||
logger.warning() << "Unable to start: tunnel process already running";
|
logger.warning() << "Unable to start: tunnel process already running";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ifname = config.m_ifname;
|
|
||||||
|
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
if (!wgRuntimeDir.exists()) {
|
if (!wgRuntimeDir.exists()) {
|
||||||
wgRuntimeDir.mkpath(".");
|
wgRuntimeDir.mkpath(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||||
QString wgNameFile = wgRuntimeDir.filePath(ifname + ".sock");
|
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".sock");
|
||||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
pe.insert("LOG_LEVEL", "debug");
|
pe.insert("LOG_LEVEL", "debug");
|
||||||
@@ -78,7 +79,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||||||
m_tunnel.setProcessEnvironment(pe);
|
m_tunnel.setProcessEnvironment(pe);
|
||||||
|
|
||||||
QDir appPath(QCoreApplication::applicationDirPath());
|
QDir appPath(QCoreApplication::applicationDirPath());
|
||||||
QStringList wgArgs = {"-f", ifname};
|
QStringList wgArgs = {"-f", "amn0"};
|
||||||
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
|
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
|
||||||
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||||
logger.error() << "Unable to start tunnel process due to timeout";
|
logger.error() << "Unable to start tunnel process due to timeout";
|
||||||
@@ -146,6 +147,29 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||||
|
} else {
|
||||||
|
if (config.m_killSwitchEnabled) {
|
||||||
|
FirewallParams params { };
|
||||||
|
params.dnsServers.append(config.m_primaryDnsServer);
|
||||||
|
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||||
|
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||||
|
}
|
||||||
|
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||||
|
params.blockAll = true;
|
||||||
|
if (config.m_excludedAddresses.size()) {
|
||||||
|
params.allowNets = true;
|
||||||
|
foreach (auto net, config.m_excludedAddresses) {
|
||||||
|
params.allowAddrs.append(net.toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params.blockNets = true;
|
||||||
|
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||||
|
params.blockAddrs.append(net.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyFirewallRules(params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (err == 0);
|
return (err == 0);
|
||||||
@@ -170,8 +194,10 @@ bool WireguardUtilsLinux::deleteInterface() {
|
|||||||
|
|
||||||
// Garbage collect.
|
// Garbage collect.
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
QFile::remove(wgRuntimeDir.filePath(m_ifname + ".name"));
|
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||||
|
|
||||||
|
// double-check + ensure our firewall is installed and enabled
|
||||||
|
KillSwitch::instance()->disableKillSwitch();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +234,13 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
|||||||
out << "allowed_ip=" << ip.toString() << "\n";
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||||
@@ -219,6 +252,13 @@ bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
|
|||||||
QByteArray publicKey =
|
QByteArray publicKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
out << "set=1\n";
|
out << "set=1\n";
|
||||||
@@ -298,7 +338,7 @@ bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prefix.prefixLength() > 0) {
|
if (prefix.prefixLength() > 0) {
|
||||||
return m_rtmonitor->deleteRoute(prefix);
|
return m_rtmonitor->insertRoute(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we do not replace the default route.
|
// Ensure that we do not replace the default route.
|
||||||
@@ -349,9 +389,13 @@ bool WireguardUtilsLinux::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
|||||||
|
|
||||||
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
|
QTimer uapiTimeout;
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||||
|
|
||||||
|
uapiTimeout.setSingleShot(true);
|
||||||
|
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||||
|
|
||||||
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||||
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||||
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||||
@@ -366,15 +410,13 @@ QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
|||||||
}
|
}
|
||||||
socket.write(message);
|
socket.write(message);
|
||||||
|
|
||||||
QElapsedTimer elapsed;
|
|
||||||
elapsed.start();
|
|
||||||
QByteArray reply;
|
QByteArray reply;
|
||||||
while (!reply.contains("\n\n")) {
|
while (!reply.contains("\n\n")) {
|
||||||
const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
|
if (!uapiTimeout.isActive()) {
|
||||||
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
|
|
||||||
logger.error() << "UAPI command timed out";
|
logger.error() << "UAPI command timed out";
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
reply.append(socket.readAll());
|
reply.append(socket.readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,15 +442,45 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
|||||||
timeout.setSingleShot(true);
|
timeout.setSingleShot(true);
|
||||||
timeout.start(WG_TUN_PROC_TIMEOUT);
|
timeout.start(WG_TUN_PROC_TIMEOUT);
|
||||||
|
|
||||||
|
QFile file(filename);
|
||||||
|
|
||||||
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
|
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
|
||||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
|
QString ifname = "amn0";
|
||||||
|
|
||||||
|
// Test-connect to the UAPI socket.
|
||||||
QLocalSocket sock;
|
QLocalSocket sock;
|
||||||
sock.connectToServer(filename, QIODevice::ReadWrite);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
|
||||||
|
sock.connectToServer(sockName, QIODevice::ReadWrite);
|
||||||
if (sock.waitForConnected(100)) {
|
if (sock.waitForConnected(100)) {
|
||||||
return QFileInfo(filename).baseName();
|
return ifname;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
|
||||||
|
{
|
||||||
|
// double-check + ensure our firewall is installed and enabled
|
||||||
|
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
|
||||||
|
|
||||||
|
// Note: rule precedence is handled inside IpTablesFirewall
|
||||||
|
LinuxFirewall::ensureRootAnchorPriority();
|
||||||
|
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
|
||||||
|
LinuxFirewall::updateAllowNets(params.allowAddrs);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
|
||||||
|
LinuxFirewall::updateBlockNets(params.blockAddrs);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
||||||
|
LinuxFirewall::updateDNSServers(params.dnsServers);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
||||||
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "daemon/wireguardutils.h"
|
#include "daemon/wireguardutils.h"
|
||||||
#include "linuxroutemonitor.h"
|
#include "linuxroutemonitor.h"
|
||||||
|
#include "linuxfirewall.h"
|
||||||
|
|
||||||
|
|
||||||
class WireguardUtilsLinux final : public WireguardUtils {
|
class WireguardUtilsLinux final : public WireguardUtils {
|
||||||
@@ -39,6 +40,7 @@ public:
|
|||||||
|
|
||||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
|
void applyFirewallRules(FirewallParams& params);
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
|
|||||||
@@ -185,9 +185,6 @@ bool DnsUtilsMacos::restoreResolvers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DnsUtilsMacos::backupService(const QString& uuid) {
|
void DnsUtilsMacos::backupService(const QString& uuid) {
|
||||||
if (m_prevServices.contains(uuid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DnsBackup backup;
|
DnsBackup backup;
|
||||||
CFStringRef path = CFStringCreateWithFormat(
|
CFStringRef path = CFStringCreateWithFormat(
|
||||||
kCFAllocatorSystemDefault, nullptr,
|
kCFAllocatorSystemDefault, nullptr,
|
||||||
|
|||||||
@@ -39,12 +39,8 @@ bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
Q_UNUSED(config);
|
||||||
if (!wg) {
|
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString ifname = wg->interfaceName();
|
|
||||||
struct ifreq ifr;
|
struct ifreq ifr;
|
||||||
|
|
||||||
// Create socket file descriptor to perform the ioctl operations on
|
// Create socket file descriptor to perform the ioctl operations on
|
||||||
@@ -84,12 +80,8 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
Q_UNUSED(config);
|
||||||
if (!wg) {
|
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString ifname = wg->interfaceName();
|
|
||||||
struct ifaliasreq ifr;
|
struct ifaliasreq ifr;
|
||||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||||
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
||||||
@@ -138,12 +130,8 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||||
WireguardUtils* wg = MacOSDaemon::instance()->wgutilsFor(config.m_ifname);
|
Q_UNUSED(config);
|
||||||
if (!wg) {
|
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||||
logger.error() << "No wireguard interface for" << config.m_ifname;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString ifname = wg->interfaceName();
|
|
||||||
struct in6_aliasreq ifr6;
|
struct in6_aliasreq ifr6;
|
||||||
|
|
||||||
// Name the interface and set family
|
// Name the interface and set family
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
|||||||
|
|
||||||
logger.debug() << "Daemon created";
|
logger.debug() << "Daemon created";
|
||||||
|
|
||||||
|
m_wgutils = new WireguardUtilsMacos(this);
|
||||||
m_dnsutils = new DnsUtilsMacos(this);
|
m_dnsutils = new DnsUtilsMacos(this);
|
||||||
m_iputils = new IPUtilsMacos(this);
|
m_iputils = new IPUtilsMacos(this);
|
||||||
|
|
||||||
@@ -49,4 +50,3 @@ MacOSDaemon* MacOSDaemon::instance() {
|
|||||||
Q_ASSERT(s_daemon);
|
Q_ASSERT(s_daemon);
|
||||||
return s_daemon;
|
return s_daemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "wireguardutilsmacos.h"
|
#include "wireguardutilsmacos.h"
|
||||||
|
|
||||||
class MacOSDaemon final : public Daemon {
|
class MacOSDaemon final : public Daemon {
|
||||||
|
friend class IPUtilsMacos;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MacOSDaemon();
|
MacOSDaemon();
|
||||||
~MacOSDaemon();
|
~MacOSDaemon();
|
||||||
@@ -18,15 +20,13 @@ class MacOSDaemon final : public Daemon {
|
|||||||
static MacOSDaemon* instance();
|
static MacOSDaemon* instance();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
bool supportIPUtils() const override { return true; }
|
bool supportIPUtils() const override { return true; }
|
||||||
IPUtils* iputils() override { return m_iputils; }
|
IPUtils* iputils() override { return m_iputils; }
|
||||||
|
|
||||||
WireguardUtils* createWgUtils() override {
|
|
||||||
return new WireguardUtilsMacos(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
WireguardUtilsMacos* m_wgutils = nullptr;
|
||||||
DnsUtilsMacos* m_dnsutils = nullptr;
|
DnsUtilsMacos* m_dnsutils = nullptr;
|
||||||
IPUtilsMacos* m_iputils = nullptr;
|
IPUtilsMacos* m_iputils = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,35 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
// Descriptor for a set of firewall rules to be appled.
|
||||||
|
//
|
||||||
|
struct FirewallParams
|
||||||
|
{
|
||||||
|
QStringList dnsServers;
|
||||||
|
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
|
||||||
|
|
||||||
|
QStringList allowAddrs;
|
||||||
|
QStringList blockAddrs;
|
||||||
|
|
||||||
|
// The follow flags indicate which general rulesets are needed. Note that
|
||||||
|
// this is after some sanity filtering, i.e. an allow rule may be listed
|
||||||
|
// as not needed if there were no block rules preceding it. The rulesets
|
||||||
|
// should be thought of as in last-match order.
|
||||||
|
|
||||||
|
bool blockAll; // Block all traffic by default
|
||||||
|
bool blockNets;
|
||||||
|
bool allowNets;
|
||||||
|
bool allowVPN; // Exempt traffic through VPN tunnel
|
||||||
|
bool allowDHCP; // Exempt DHCP traffic
|
||||||
|
bool blockIPv6; // Block all IPv6 traffic
|
||||||
|
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
|
||||||
|
bool blockDNS; // Block all DNS traffic except specified DNS servers
|
||||||
|
bool allowPIA; // Exempt PIA executables
|
||||||
|
bool allowLoopback; // Exempt loopback traffic
|
||||||
|
bool allowHnsd; // Exempt Handshake DNS traffic
|
||||||
|
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
|
||||||
|
};
|
||||||
|
|
||||||
class MacOSFirewall
|
class MacOSFirewall
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
|
|||||||
|
|
||||||
MacosRouteMonitor::~MacosRouteMonitor() {
|
MacosRouteMonitor::~MacosRouteMonitor() {
|
||||||
MZ_COUNT_DTOR(MacosRouteMonitor);
|
MZ_COUNT_DTOR(MacosRouteMonitor);
|
||||||
|
flushExclusionRoutes();
|
||||||
if (m_rtsock >= 0) {
|
if (m_rtsock >= 0) {
|
||||||
close(m_rtsock);
|
close(m_rtsock);
|
||||||
}
|
}
|
||||||
@@ -435,15 +436,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ((action == RTM_ADD) && (errno == EEXIST)) {
|
if ((action == RTM_ADD) && (errno == EEXIST)) {
|
||||||
rtm->rtm_type = RTM_DELETE;
|
return true;
|
||||||
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)) {
|
if ((action == RTM_DELETE) && (errno == ESRCH)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -17,6 +16,8 @@
|
|||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include "killswitch.h"
|
||||||
|
|
||||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||||
|
|
||||||
@@ -57,20 +58,19 @@ void WireguardUtilsMacos::tunnelErrorOccurred(QProcess::ProcessError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||||
|
Q_UNUSED(config);
|
||||||
if (m_tunnel.state() != QProcess::NotRunning) {
|
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||||
logger.warning() << "Unable to start: tunnel process already running";
|
logger.warning() << "Unable to start: tunnel process already running";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString ifname = config.m_ifname;
|
|
||||||
|
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
if (!wgRuntimeDir.exists()) {
|
if (!wgRuntimeDir.exists()) {
|
||||||
wgRuntimeDir.mkpath(".");
|
wgRuntimeDir.mkpath(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||||
QString wgNameFile = wgRuntimeDir.filePath(ifname + ".name");
|
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name");
|
||||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
pe.insert("LOG_LEVEL", "debug");
|
pe.insert("LOG_LEVEL", "debug");
|
||||||
@@ -92,7 +92,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||||||
m_tunnel.kill();
|
m_tunnel.kill();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QFile::remove(wgNameFile);
|
|
||||||
logger.debug() << "Created wireguard interface" << m_ifname;
|
logger.debug() << "Created wireguard interface" << m_ifname;
|
||||||
|
|
||||||
// Start the routing table monitor.
|
// Start the routing table monitor.
|
||||||
@@ -146,6 +145,30 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||||
|
} else {
|
||||||
|
if (config.m_killSwitchEnabled) {
|
||||||
|
FirewallParams params { };
|
||||||
|
params.dnsServers.append(config.m_primaryDnsServer);
|
||||||
|
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||||
|
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||||
|
params.blockAll = true;
|
||||||
|
if (config.m_excludedAddresses.size()) {
|
||||||
|
params.allowNets = true;
|
||||||
|
foreach (auto net, config.m_excludedAddresses) {
|
||||||
|
params.allowAddrs.append(net.toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params.blockNets = true;
|
||||||
|
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||||
|
params.blockAddrs.append(net.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyFirewallRules(params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (err == 0);
|
return (err == 0);
|
||||||
}
|
}
|
||||||
@@ -167,6 +190,13 @@ bool WireguardUtilsMacos::deleteInterface() {
|
|||||||
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
|
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Garbage collect.
|
||||||
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
|
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||||
|
|
||||||
|
// double-check + ensure our firewall is installed and enabled
|
||||||
|
KillSwitch::instance()->disableKillSwitch();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +234,13 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||||||
out << "allowed_ip=" << ip.toString() << "\n";
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||||
@@ -215,6 +252,13 @@ bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
|||||||
QByteArray publicKey =
|
QByteArray publicKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||||
|
(m_rtmonitor != nullptr)) {
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
out << "set=1\n";
|
out << "set=1\n";
|
||||||
@@ -345,9 +389,13 @@ bool WireguardUtilsMacos::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
|||||||
|
|
||||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
|
QTimer uapiTimeout;
|
||||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||||
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||||
|
|
||||||
|
uapiTimeout.setSingleShot(true);
|
||||||
|
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||||
|
|
||||||
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||||
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||||
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||||
@@ -362,15 +410,13 @@ QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
|||||||
}
|
}
|
||||||
socket.write(message);
|
socket.write(message);
|
||||||
|
|
||||||
QElapsedTimer elapsed;
|
|
||||||
elapsed.start();
|
|
||||||
QByteArray reply;
|
QByteArray reply;
|
||||||
while (!reply.contains("\n\n")) {
|
while (!reply.contains("\n\n")) {
|
||||||
const qint64 remaining = WG_TUN_PROC_TIMEOUT - elapsed.elapsed();
|
if (!uapiTimeout.isActive()) {
|
||||||
if (remaining <= 0 || !socket.waitForReadyRead(static_cast<int>(remaining))) {
|
|
||||||
logger.error() << "UAPI command timed out";
|
logger.error() << "UAPI command timed out";
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||||
reply.append(socket.readAll());
|
reply.append(socket.readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,3 +463,28 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
|
|||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
|
||||||
|
{
|
||||||
|
// double-check + ensure our firewall is installed and enabled. This is necessary as
|
||||||
|
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
|
||||||
|
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
|
||||||
|
|
||||||
|
MacOSFirewall::ensureRootAnchorPriority();
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
|
||||||
|
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
|
||||||
|
QStringLiteral("allownets"), params.allowAddrs);
|
||||||
|
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
|
||||||
|
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
|
||||||
|
QStringLiteral("blocknets"), params.blockAddrs);
|
||||||
|
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
|
||||||
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
|
||||||
|
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "daemon/wireguardutils.h"
|
#include "daemon/wireguardutils.h"
|
||||||
#include "macosroutemonitor.h"
|
#include "macosroutemonitor.h"
|
||||||
|
#include "macosfirewall.h"
|
||||||
|
|
||||||
class WireguardUtilsMacos final : public WireguardUtils {
|
class WireguardUtilsMacos final : public WireguardUtils {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -37,6 +38,8 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
|||||||
|
|
||||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||||
|
|
||||||
|
void applyFirewallRules(FirewallParams& params);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,14 @@ WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
|
|||||||
m_firewallManager = WindowsFirewall::create(this);
|
m_firewallManager = WindowsFirewall::create(this);
|
||||||
Q_ASSERT(m_firewallManager != nullptr);
|
Q_ASSERT(m_firewallManager != nullptr);
|
||||||
|
|
||||||
|
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
|
||||||
m_dnsutils = new DnsUtilsWindows(this);
|
m_dnsutils = new DnsUtilsWindows(this);
|
||||||
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
||||||
|
|
||||||
|
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
|
||||||
|
&WindowsDaemon::monitorBackendFailure);
|
||||||
|
connect(this, &WindowsDaemon::activationFailure,
|
||||||
|
[this]() { m_firewallManager->disableKillSwitch(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsDaemon::~WindowsDaemon() {
|
WindowsDaemon::~WindowsDaemon() {
|
||||||
@@ -106,11 +112,3 @@ void WindowsDaemon::monitorBackendFailure() {
|
|||||||
emit backendFailure();
|
emit backendFailure();
|
||||||
deactivate();
|
deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
WireguardUtils* WindowsDaemon::createWgUtils() {
|
|
||||||
auto utils = WireguardUtilsWindows::create(m_firewallManager, this);
|
|
||||||
if (!utils) return nullptr;
|
|
||||||
connect(utils.get(), &WireguardUtilsWindows::backendFailure, this,
|
|
||||||
&WindowsDaemon::monitorBackendFailure);
|
|
||||||
return utils.release();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class WindowsDaemon final : public Daemon {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool run(Op op, const InterfaceConfig& config) override;
|
bool run(Op op, const InterfaceConfig& config) override;
|
||||||
|
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
|
||||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||||
WireguardUtils* createWgUtils() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void monitorBackendFailure();
|
void monitorBackendFailure();
|
||||||
@@ -42,6 +42,7 @@ class WindowsDaemon final : public Daemon {
|
|||||||
|
|
||||||
int m_inetAdapterIndex = -1;
|
int m_inetAdapterIndex = -1;
|
||||||
|
|
||||||
|
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
|
||||||
DnsUtilsWindows* m_dnsutils = nullptr;
|
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||||
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
||||||
QPointer<WindowsFirewall> m_firewallManager;
|
QPointer<WindowsFirewall> m_firewallManager;
|
||||||
|
|||||||
@@ -37,14 +37,11 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
|
|||||||
QCoreApplication::setApplicationName("Amnezia VPN Tunnel");
|
QCoreApplication::setApplicationName("Amnezia VPN Tunnel");
|
||||||
QCoreApplication::setApplicationVersion(Constants::versionString());
|
QCoreApplication::setApplicationVersion(Constants::versionString());
|
||||||
|
|
||||||
if (tokens.length() < 2 || tokens.length() > 3) {
|
if (tokens.length() != 2) {
|
||||||
logger.error() << "Expected: <config> [<ifname>]";
|
logger.error() << "Expected 1 parameter only: the config file.";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
QString maybeConfig = tokens.at(1);
|
QString maybeConfig = tokens.at(1);
|
||||||
QString name = tokens.length() == 3 && !tokens.at(2).isEmpty()
|
|
||||||
? tokens.at(2)
|
|
||||||
: WireguardUtilsWindows::s_defaultInterfaceName();
|
|
||||||
|
|
||||||
if (!maybeConfig.startsWith("[Interface]")) {
|
if (!maybeConfig.startsWith("[Interface]")) {
|
||||||
logger.error() << "parameter Does not seem to be a config";
|
logger.error() << "parameter Does not seem to be a config";
|
||||||
@@ -67,6 +64,7 @@ int WindowsDaemonTunnel::run(QStringList& tokens) {
|
|||||||
WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function");
|
WindowsUtils::windowsLog("Failed to get WireGuardTunnelService function");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
auto name = WireguardUtilsWindows::s_interfaceName();
|
||||||
if (!tunnelProc(maybeConfig.utf16(), name.utf16())) {
|
if (!tunnelProc(maybeConfig.utf16(), name.utf16())) {
|
||||||
logger.error() << "Failed to activate the tunnel service";
|
logger.error() << "Failed to activate the tunnel service";
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ bool WindowsFirewall::initSublayer() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::enableInterface(int vpnAdapterIndex, const QString& ifname) {
|
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||||
// Checks if the FW_Rule was enabled succesfully,
|
// Checks if the FW_Rule was enabled succesfully,
|
||||||
// disables the whole killswitch and returns false if not.
|
// disables the whole killswitch and returns false if not.
|
||||||
#define FW_OK(rule) \
|
#define FW_OK(rule) \
|
||||||
@@ -182,39 +182,31 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex, const QString& ifname
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex
|
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||||
<< "ifname:" << ifname;
|
if (vpnAdapterIndex < 0)
|
||||||
|
{
|
||||||
QList<uint64_t>& perTunnel = ifname.isEmpty() ? m_globalRules
|
|
||||||
: m_tunnelRules[ifname];
|
|
||||||
if (vpnAdapterIndex < 0) {
|
|
||||||
IPAddress allv4("0.0.0.0/0");
|
IPAddress allv4("0.0.0.0/0");
|
||||||
if (!blockTrafficTo(allv4, MED_WEIGHT, "Block Internet", perTunnel)) {
|
if (!blockTrafficTo(allv4, MED_WEIGHT,
|
||||||
return false;
|
"Block Internet", "killswitch")) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
IPAddress allv6("::/0");
|
IPAddress allv6("::/0");
|
||||||
if (!blockTrafficTo(allv6, MED_WEIGHT, "Block Internet", perTunnel)) {
|
if (!blockTrafficTo(allv6, MED_WEIGHT,
|
||||||
|
"Block Internet", "killswitch")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else
|
||||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||||
"Allow usage of VPN Adapter", perTunnel));
|
"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"));
|
||||||
|
|
||||||
if (m_globalRules.isEmpty()) {
|
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
|
||||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic", m_globalRules));
|
|
||||||
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic", m_globalRules));
|
|
||||||
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
|
||||||
"Allow all for AmneziaVPN.exe", m_globalRules));
|
|
||||||
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS", m_globalRules));
|
|
||||||
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
|
|
||||||
"Allow Loopback traffic on device %1",
|
|
||||||
m_globalRules));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug() << "Killswitch on! Globals:" << m_globalRules.length()
|
|
||||||
<< "Tunnel[" << ifname
|
|
||||||
<< "]:" << m_tunnelRules.value(ifname).length();
|
|
||||||
return true;
|
return true;
|
||||||
#undef FW_OK
|
#undef FW_OK
|
||||||
}
|
}
|
||||||
@@ -234,8 +226,7 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
|||||||
|
|
||||||
// Blocking unprotected traffic
|
// Blocking unprotected traffic
|
||||||
for (const IPAddress& prefix : ranges) {
|
for (const IPAddress& prefix : ranges) {
|
||||||
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic",
|
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
|
||||||
m_globalRules)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,10 +242,7 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allow unprotected traffic sent to the following address ranges.
|
// Allow unprotected traffic sent to the following address ranges.
|
||||||
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString& ifname) {
|
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||||
QList<uint64_t>& target = ifname.isEmpty() ? m_globalRules
|
|
||||||
: m_tunnelRules[ifname];
|
|
||||||
|
|
||||||
// Start the firewall transaction
|
// Start the firewall transaction
|
||||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
if (result != ERROR_SUCCESS) {
|
if (result != ERROR_SUCCESS) {
|
||||||
@@ -267,9 +255,8 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const QString& addr : ranges) {
|
for (const QString& addr : ranges) {
|
||||||
logger.debug() << "Allow killswitch exclude: " << addr << "ifname:" << ifname;
|
logger.debug() << "Allow killswitch exclude: " << addr;
|
||||||
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT,
|
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
|
||||||
"Allow killswitch bypass traffic", target)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,10 +273,6 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString
|
|||||||
|
|
||||||
|
|
||||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||||
QList<uint64_t>& target = config.m_ifname.isEmpty()
|
|
||||||
? m_globalRules
|
|
||||||
: m_tunnelRules[config.m_ifname];
|
|
||||||
|
|
||||||
// Start the firewall transaction
|
// Start the firewall transaction
|
||||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
if (result != ERROR_SUCCESS) {
|
if (result != ERROR_SUCCESS) {
|
||||||
@@ -305,12 +288,12 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
logger.info() << "Enabling traffic for peer"
|
logger.info() << "Enabling traffic for peer"
|
||||||
<< config.m_serverPublicKey;
|
<< config.m_serverPublicKey;
|
||||||
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
|
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
|
||||||
"Block Internet", target)) {
|
"Block Internet", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!config.m_primaryDnsServer.isEmpty()) {
|
if (!config.m_primaryDnsServer.isEmpty()) {
|
||||||
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
|
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
|
||||||
"Allow DNS-Server", target)) {
|
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||||
@@ -319,7 +302,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||||
target)) {
|
config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +310,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
|
|
||||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||||
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
|
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
|
||||||
"Allow DNS-Server", target)) {
|
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||||
@@ -336,7 +319,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
|
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
|
||||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||||
target)) {
|
config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +328,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
for (const QString& dns : config.m_allowedDnsServers) {
|
for (const QString& dns : config.m_allowedDnsServers) {
|
||||||
logger.debug() << "Allow DNS: " << dns;
|
logger.debug() << "Allow DNS: " << dns;
|
||||||
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
|
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
|
||||||
"Allow DNS-Server", target)) {
|
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +338,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
logger.debug() << "excludedAddresses range: " << i;
|
logger.debug() << "excludedAddresses range: " << i;
|
||||||
|
|
||||||
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||||
"Allow Ecxlude route", target)) {
|
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,6 +354,35 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
|
||||||
|
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||||
|
auto cleanup = qScopeGuard([&] {
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
FwpmTransactionAbort0(m_sessionHandle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info() << "Disabling traffic for peer" << pubkey;
|
||||||
|
for (const auto& filterID : m_peerRules.values(pubkey)) {
|
||||||
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
|
m_peerRules.remove(pubkey, filterID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit!
|
||||||
|
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||||
|
<< result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::disableKillSwitch() {
|
bool WindowsFirewall::disableKillSwitch() {
|
||||||
return KillSwitch::instance()->disableKillSwitch();
|
return KillSwitch::instance()->disableKillSwitch();
|
||||||
}
|
}
|
||||||
@@ -388,13 +400,11 @@ bool WindowsFirewall::allowAllTraffic() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& bucket : qAsConst(m_tunnelRules)) {
|
for (const auto& filterID : m_peerRules.values()) {
|
||||||
for (const auto& filterID : bucket) {
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& filterID : qAsConst(m_globalRules)) {
|
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,42 +415,15 @@ bool WindowsFirewall::allowAllTraffic() {
|
|||||||
<< result;
|
<< result;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_tunnelRules.clear();
|
m_peerRules.clear();
|
||||||
m_globalRules.clear();
|
m_activeRules.clear();
|
||||||
logger.debug() << "Firewall Disabled!";
|
logger.debug() << "Firewall Disabled!";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::disableKillSwitchForTunnel(const QString& ifname) {
|
|
||||||
if (ifname.isEmpty() || !m_tunnelRules.contains(ifname)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
|
||||||
if (result != ERROR_SUCCESS) {
|
|
||||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:" << result;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<uint64_t> filters = m_tunnelRules.take(ifname);
|
|
||||||
logger.info() << "Disabling killswitch filters for tunnel" << ifname
|
|
||||||
<< "count:" << filters.length();
|
|
||||||
for (const auto& filterID : filters) {
|
|
||||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
|
||||||
if (result != ERROR_SUCCESS) {
|
|
||||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:" << result;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||||
int weight,
|
int weight,
|
||||||
const QString& title,
|
const QString& title) {
|
||||||
QList<uint64_t>& target) {
|
|
||||||
DWORD result = ERROR_SUCCESS;
|
DWORD result = ERROR_SUCCESS;
|
||||||
Q_ASSERT(weight <= 15);
|
Q_ASSERT(weight <= 15);
|
||||||
|
|
||||||
@@ -477,7 +460,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
|||||||
{
|
{
|
||||||
QString desc("Permit (out) IPv4 Traffic of: " + appName);
|
QString desc("Permit (out) IPv4 Traffic of: " + appName);
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
if (!enableFilter(&filter, title, desc, target)) {
|
if (!enableFilter(&filter, title, desc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,7 +468,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
|||||||
{
|
{
|
||||||
QString desc("Permit (in) IPv4 Traffic of: " + appName);
|
QString desc("Permit (in) IPv4 Traffic of: " + appName);
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
if (!enableFilter(&filter, title, desc, target)) {
|
if (!enableFilter(&filter, title, desc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,8 +476,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
const QString& title,
|
const QString& title) {
|
||||||
QList<uint64_t>& target) {
|
|
||||||
FWPM_FILTER_CONDITION0 conds;
|
FWPM_FILTER_CONDITION0 conds;
|
||||||
// Condition: Request must be targeting the TUN interface
|
// Condition: Request must be targeting the TUN interface
|
||||||
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
|
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
|
||||||
@@ -516,25 +498,25 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
|||||||
// #1 Permit outbound IPv4 traffic.
|
// #1 Permit outbound IPv4 traffic.
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("out").arg(networkAdapter), target)) {
|
description.arg("out").arg(networkAdapter))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// #2 Permit inbound IPv4 traffic.
|
// #2 Permit inbound IPv4 traffic.
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("in").arg(networkAdapter), target)) {
|
description.arg("in").arg(networkAdapter))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// #3 Permit outbound IPv6 traffic.
|
// #3 Permit outbound IPv6 traffic.
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("out").arg(networkAdapter), target)) {
|
description.arg("out").arg(networkAdapter))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// #4 Permit inbound IPv6 traffic.
|
// #4 Permit inbound IPv6 traffic.
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("in").arg(networkAdapter), target)) {
|
description.arg("in").arg(networkAdapter))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -542,7 +524,7 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
|||||||
|
|
||||||
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||||
const QString& title,
|
const QString& title,
|
||||||
QList<uint64_t>& target) {
|
const QString& peer) {
|
||||||
GUID layerKeyOut;
|
GUID layerKeyOut;
|
||||||
GUID layerKeyIn;
|
GUID layerKeyIn;
|
||||||
if (addr.type() == QAbstractSocket::IPv4Protocol) {
|
if (addr.type() == QAbstractSocket::IPv4Protocol) {
|
||||||
@@ -580,11 +562,11 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
|||||||
// Send the filters down to the firewall.
|
// Send the filters down to the firewall.
|
||||||
QString description = "Permit traffic %1 " + addr.toString();
|
QString description = "Permit traffic %1 " + addr.toString();
|
||||||
filter.layerKey = layerKeyOut;
|
filter.layerKey = layerKeyOut;
|
||||||
if (!enableFilter(&filter, title, description.arg("to"), target)) {
|
if (!enableFilter(&filter, title, description.arg("to"), peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter.layerKey = layerKeyIn;
|
filter.layerKey = layerKeyIn;
|
||||||
if (!enableFilter(&filter, title, description.arg("from"), target)) {
|
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -592,7 +574,7 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
|||||||
|
|
||||||
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||||
int weight, const QString& title,
|
int weight, const QString& title,
|
||||||
QList<uint64_t>& target) {
|
const QString& peer) {
|
||||||
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||||
GUID layerOut =
|
GUID layerOut =
|
||||||
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
@@ -641,20 +623,19 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
|||||||
filter.layerKey = layerOut;
|
filter.layerKey = layerOut;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("to").arg(targetIP.toString()).arg(port),
|
description.arg("to").arg(targetIP.toString()).arg(port),
|
||||||
target)) {
|
peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter.layerKey = layerIn;
|
filter.layerKey = layerIn;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("from").arg(targetIP.toString()).arg(port),
|
description.arg("from").arg(targetIP.toString()).arg(port),
|
||||||
target)) {
|
peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||||
QList<uint64_t>& target) {
|
|
||||||
// Allow outbound DHCPv4
|
// Allow outbound DHCPv4
|
||||||
{
|
{
|
||||||
FWPM_FILTER_CONDITION0 conds[4];
|
FWPM_FILTER_CONDITION0 conds[4];
|
||||||
@@ -691,7 +672,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
|||||||
|
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
|
|
||||||
if (!enableFilter(&filter, title, "Allow Outbound DHCP", target)) {
|
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,7 +705,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
|||||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
|
|
||||||
if (!enableFilter(&filter, title, "Allow inbound DHCP", target)) {
|
if (!enableFilter(&filter, title, "Allow inbound DHCP")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -759,7 +740,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
|||||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
|
|
||||||
if (!enableFilter(&filter, title, "Allow outbound DHCPv6", target)) {
|
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -792,7 +773,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
|||||||
filter.weight.uint8 = weight;
|
filter.weight.uint8 = weight;
|
||||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
if (!enableFilter(&filter, title, "Allow inbound DHCPv6", target)) {
|
if (!enableFilter(&filter, title, "Allow inbound DHCPv6")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,8 +781,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allows the internal Hyper-V Switches to work.
|
// Allows the internal Hyper-V Switches to work.
|
||||||
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
|
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
|
||||||
QList<uint64_t>& target) {
|
|
||||||
FWPM_FILTER_CONDITION0 cond;
|
FWPM_FILTER_CONDITION0 cond;
|
||||||
// Condition: Request must be targeting the TUN interface
|
// Condition: Request must be targeting the TUN interface
|
||||||
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
|
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
|
||||||
@@ -821,12 +801,12 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
|
|||||||
|
|
||||||
// #1 Permit Hyper-V => Hyper-V outbound.
|
// #1 Permit Hyper-V => Hyper-V outbound.
|
||||||
filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE;
|
filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE;
|
||||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound", target)) {
|
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// #2 Permit Hyper-V => Hyper-V inbound.
|
// #2 Permit Hyper-V => Hyper-V inbound.
|
||||||
filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE;
|
filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE;
|
||||||
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound", target)) {
|
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -834,7 +814,7 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
|
|||||||
|
|
||||||
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||||
const QString& title,
|
const QString& title,
|
||||||
QList<uint64_t>& target) {
|
const QString& peer) {
|
||||||
QString description("Block traffic %1 %2 ");
|
QString description("Block traffic %1 %2 ");
|
||||||
|
|
||||||
auto lower = addr.address();
|
auto lower = addr.address();
|
||||||
@@ -872,12 +852,12 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
|||||||
|
|
||||||
filter.layerKey = layerKeyOut;
|
filter.layerKey = layerKeyOut;
|
||||||
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
||||||
target)) {
|
peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter.layerKey = layerKeyIn;
|
filter.layerKey = layerKeyIn;
|
||||||
if (!enableFilter(&filter, title,
|
if (!enableFilter(&filter, title,
|
||||||
description.arg("from").arg(addr.toString()), target)) {
|
description.arg("from").arg(addr.toString()), peer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -885,9 +865,9 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
|||||||
|
|
||||||
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
|
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
|
||||||
uint8_t weight, const QString& title,
|
uint8_t weight, const QString& title,
|
||||||
QList<uint64_t>& target) {
|
const QString& peer) {
|
||||||
for (auto range : rangeList) {
|
for (auto range : rangeList) {
|
||||||
if (!blockTrafficTo(range, weight, title, target)) {
|
if (!blockTrafficTo(range, weight, title, peer)) {
|
||||||
logger.info() << "Setting Range of" << range.toString() << "failed";
|
logger.info() << "Setting Range of" << range.toString() << "failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -943,8 +923,7 @@ void WindowsFirewall::importAddress(const QHostAddress& addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
||||||
const QString& title,
|
const QString& title) {
|
||||||
QList<uint64_t>& target) {
|
|
||||||
// Allow Traffic to IP with PORT using any protocol
|
// Allow Traffic to IP with PORT using any protocol
|
||||||
FWPM_FILTER_CONDITION0 conds[3];
|
FWPM_FILTER_CONDITION0 conds[3];
|
||||||
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
|
||||||
@@ -974,20 +953,20 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
|||||||
|
|
||||||
QString description("Block %1 on Port %2");
|
QString description("Block %1 on Port %2");
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||||
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port), target)) {
|
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||||
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port), target)) {
|
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||||
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port), target)) {
|
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||||
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port), target)) {
|
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -995,7 +974,7 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
|
|||||||
|
|
||||||
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||||
const QString& description,
|
const QString& description,
|
||||||
QList<uint64_t>& target) {
|
const QString& peer) {
|
||||||
uint64_t filterID = 0;
|
uint64_t filterID = 0;
|
||||||
auto name = title.toStdWString();
|
auto name = title.toStdWString();
|
||||||
auto desc = description.toStdWString();
|
auto desc = description.toStdWString();
|
||||||
@@ -1008,12 +987,16 @@ bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
logger.info() << "Filter added: " << title << ":" << description;
|
logger.info() << "Filter added: " << title << ":" << description;
|
||||||
target.append(filterID);
|
if (peer.isEmpty()) {
|
||||||
|
m_activeRules.append(filterID);
|
||||||
|
} else {
|
||||||
|
m_peerRules.insert(peer, filterID);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, const QString& title,
|
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
|
||||||
QList<uint64_t>& target) {
|
const QString& title) {
|
||||||
QList<QNetworkInterface> networkInterfaces =
|
QList<QNetworkInterface> networkInterfaces =
|
||||||
QNetworkInterface::allInterfaces();
|
QNetworkInterface::allInterfaces();
|
||||||
for (const auto& iface : networkInterfaces) {
|
for (const auto& iface : networkInterfaces) {
|
||||||
@@ -1021,7 +1004,7 @@ bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, const QString& title,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!allowTrafficOfAdapter(iface.index(), weight,
|
if (!allowTrafficOfAdapter(iface.index(), weight,
|
||||||
title.arg(iface.name()), target)) {
|
title.arg(iface.name()))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QMap>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
@@ -39,42 +38,38 @@ class WindowsFirewall final : public QObject {
|
|||||||
static WindowsFirewall* create(QObject* parent);
|
static WindowsFirewall* create(QObject* parent);
|
||||||
~WindowsFirewall() override;
|
~WindowsFirewall() override;
|
||||||
|
|
||||||
bool enableInterface(int vpnAdapterIndex, const QString& ifname = QString());
|
bool enableInterface(int vpnAdapterIndex);
|
||||||
bool enableLanBypass(const QList<IPAddress>& ranges);
|
bool enableLanBypass(const QList<IPAddress>& ranges);
|
||||||
bool enablePeerTraffic(const InterfaceConfig& config);
|
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||||
|
bool disablePeerTraffic(const QString& pubkey);
|
||||||
bool disableKillSwitch();
|
bool disableKillSwitch();
|
||||||
bool disableKillSwitchForTunnel(const QString& ifname);
|
|
||||||
bool allowAllTraffic();
|
bool allowAllTraffic();
|
||||||
bool allowTrafficRange(const QStringList& ranges, const QString& ifname = QString());
|
bool allowTrafficRange(const QStringList& ranges);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool initSublayer();
|
static bool initSublayer();
|
||||||
WindowsFirewall(HANDLE session, QObject* parent);
|
WindowsFirewall(HANDLE session, QObject* parent);
|
||||||
HANDLE m_sessionHandle;
|
HANDLE m_sessionHandle;
|
||||||
bool m_init = false;
|
bool m_init = false;
|
||||||
QList<uint64_t> m_globalRules;
|
QList<uint64_t> m_activeRules;
|
||||||
QMap<QString, QList<uint64_t>> m_tunnelRules;
|
QMultiMap<QString, uint64_t> m_peerRules;
|
||||||
|
|
||||||
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
|
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
|
||||||
const QString& title, QList<uint64_t>& target);
|
const QString& title);
|
||||||
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
|
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
|
||||||
const QString& title, QList<uint64_t>& target);
|
const QString& title, const QString& peer = QString());
|
||||||
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||||
const QString& title, QList<uint64_t>& target);
|
const QString& title, const QString& peer = QString());
|
||||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title,
|
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||||
QList<uint64_t>& target);
|
|
||||||
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
|
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
|
||||||
QList<uint64_t>& target);
|
const QString& peer = QString());
|
||||||
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||||
const QString& title, QList<uint64_t>& target);
|
const QString& title, const QString& peer = QString());
|
||||||
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||||
const QString& title, QList<uint64_t>& target);
|
const QString& title);
|
||||||
bool allowDHCPTraffic(uint8_t weight, const QString& title,
|
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||||
QList<uint64_t>& target);
|
bool allowHyperVTraffic(uint8_t weight, const QString& title);
|
||||||
bool allowHyperVTraffic(uint8_t weight, const QString& title,
|
bool allowLoopbackTraffic(uint8_t weight, const QString& title);
|
||||||
QList<uint64_t>& target);
|
|
||||||
bool allowLoopbackTraffic(uint8_t weight, const QString& title,
|
|
||||||
QList<uint64_t>& target);
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
QString getCurrentPath();
|
QString getCurrentPath();
|
||||||
@@ -83,7 +78,8 @@ class WindowsFirewall final : public QObject {
|
|||||||
void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value,
|
void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value,
|
||||||
OUT QByteArray* v6DataBuffer);
|
OUT QByteArray* v6DataBuffer);
|
||||||
bool enableFilter(FWPM_FILTER0* filter, const QString& title,
|
bool enableFilter(FWPM_FILTER0* filter, const QString& title,
|
||||||
const QString& description, QList<uint64_t>& target);
|
const QString& description,
|
||||||
|
const QString& peer = QString());
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WINDOWSFIREWALL_H
|
#endif // WINDOWSFIREWALL_H
|
||||||
|
|||||||
@@ -58,12 +58,9 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<quint64> WindowsRouteMonitor::s_vpnLuids;
|
|
||||||
|
|
||||||
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||||
: QObject(parent), m_luid(luid) {
|
: QObject(parent), m_luid(luid) {
|
||||||
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||||
s_vpnLuids.insert(luid);
|
|
||||||
logger.debug() << "WindowsRouteMonitor created.";
|
logger.debug() << "WindowsRouteMonitor created.";
|
||||||
|
|
||||||
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
NotifyRouteChange2(AF_INET, routeChangeCallback, this, FALSE, &m_routeHandle);
|
||||||
@@ -72,8 +69,8 @@ WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
|||||||
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||||
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||||
CancelMibChangeNotify2(m_routeHandle);
|
CancelMibChangeNotify2(m_routeHandle);
|
||||||
s_vpnLuids.remove(m_luid);
|
|
||||||
|
|
||||||
|
flushRouteTable(m_exclusionRoutes);
|
||||||
flushRouteTable(m_clonedRoutes);
|
flushRouteTable(m_clonedRoutes);
|
||||||
logger.debug() << "WindowsRouteMonitor destroyed.";
|
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||||
}
|
}
|
||||||
@@ -98,8 +95,7 @@ void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
|
|||||||
// Rebuild the list of interfaces that are valid for routing.
|
// Rebuild the list of interfaces that are valid for routing.
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
MIB_IPINTERFACE_ROW* row = &table->Table[i];
|
||||||
// Skip any VPN wintun (own or sibling) so exclusion routes never pick one.
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!row->Connected) {
|
if (!row->Connected) {
|
||||||
@@ -130,8 +126,8 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
|||||||
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
nexthop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
// Skip any VPN wintun (own or sibling).
|
// Ignore routes into the VPN interface.
|
||||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||||
@@ -243,16 +239,14 @@ QHostAddress WindowsRouteMonitor::prefixToAddress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsRouteMonitor::isRouteExcluded(void* ptable,
|
bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const {
|
||||||
const IP_ADDRESS_PREFIX* dest) const {
|
auto i = m_exclusionRoutes.constBegin();
|
||||||
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
while (i != m_exclusionRoutes.constEnd()) {
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
const MIB_IPFORWARD_ROW2* row = i.value();
|
||||||
const MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
|
||||||
if (row->Protocol != MIB_IPPROTO_NETMGMT) continue;
|
|
||||||
if (row->Metric != EXCLUSION_ROUTE_METRIC) continue;
|
|
||||||
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -278,8 +272,8 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
|||||||
|
|
||||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||||
// Skip any VPN wintun (own or sibling).
|
// Ignore routes into the VPN interface.
|
||||||
if (s_vpnLuids.contains(row->InterfaceLuid.Value)) {
|
if (row->InterfaceLuid.Value == m_luid) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore the default route
|
// Ignore the default route
|
||||||
@@ -292,7 +286,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore routes which should be excluded.
|
// Ignore routes which should be excluded.
|
||||||
if (isRouteExcluded(table, &row->DestinationPrefix)) {
|
if (isRouteExcluded(&row->DestinationPrefix)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
||||||
@@ -381,6 +375,11 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_exclusionRoutes.contains(prefix)) {
|
||||||
|
logger.warning() << "Exclusion route already exists";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate and initialize the MIB routing table row.
|
// Allocate and initialize the MIB routing table row.
|
||||||
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
MIB_IPFORWARD_ROW2* data = new MIB_IPFORWARD_ROW2;
|
||||||
InitializeIpForwardEntry(data);
|
InitializeIpForwardEntry(data);
|
||||||
@@ -428,8 +427,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||||||
updateCapturedRoutes(family, table);
|
updateCapturedRoutes(family, table);
|
||||||
updateExclusionRoute(data, table);
|
updateExclusionRoute(data, table);
|
||||||
FreeMibTable(table);
|
FreeMibTable(table);
|
||||||
delete data;
|
|
||||||
m_ownedExclusionRoutes.insert(prefix);
|
m_exclusionRoutes[prefix] = data;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,39 +436,23 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
|||||||
logger.debug() << "Deleting exclusion route for"
|
logger.debug() << "Deleting exclusion route for"
|
||||||
<< prefix.address().toString();
|
<< prefix.address().toString();
|
||||||
|
|
||||||
m_ownedExclusionRoutes.remove(prefix);
|
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||||
|
if (data == nullptr) {
|
||||||
PMIB_IPFORWARD_TABLE2 table;
|
return true;
|
||||||
DWORD result = GetIpForwardTable2(AF_UNSPEC, &table);
|
|
||||||
if (result != NO_ERROR) {
|
|
||||||
logger.error() << "Failed to fetch routing table:" << result;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool isV4 = prefix.address().protocol() == QAbstractSocket::IPv4Protocol;
|
DWORD result = DeleteIpForwardEntry2(data);
|
||||||
const ADDRESS_FAMILY addrFamily =
|
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||||
isV4 ? static_cast<ADDRESS_FAMILY>(AF_INET)
|
logger.error() << "Failed to delete route to"
|
||||||
: static_cast<ADDRESS_FAMILY>(AF_INET6);
|
<< prefix.toString()
|
||||||
bool deleted = false;
|
<< "result:" << result;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
FreeMibTable(table);
|
|
||||||
updateCapturedRoutes(addrFamily);
|
// Captured routes might have changed.
|
||||||
return deleted;
|
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
|
||||||
|
|
||||||
|
delete data;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsRouteMonitor::flushRouteTable(
|
void WindowsRouteMonitor::flushRouteTable(
|
||||||
@@ -509,24 +492,8 @@ void WindowsRouteMonitor::routeChanged() {
|
|||||||
|
|
||||||
updateInterfaceMetrics(AF_UNSPEC);
|
updateInterfaceMetrics(AF_UNSPEC);
|
||||||
updateCapturedRoutes(AF_UNSPEC, table);
|
updateCapturedRoutes(AF_UNSPEC, table);
|
||||||
|
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
||||||
for (const IPAddress& prefix : m_ownedExclusionRoutes) {
|
updateExclusionRoute(data, table);
|
||||||
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);
|
FreeMibTable(table);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSet>
|
|
||||||
|
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
||||||
@@ -29,6 +28,7 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
|
|
||||||
bool addExclusionRoute(const IPAddress& prefix);
|
bool addExclusionRoute(const IPAddress& prefix);
|
||||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||||
|
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
|
||||||
|
|
||||||
quint64 getLuid() const { return m_luid; }
|
quint64 getLuid() const { return m_luid; }
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
void routeChanged();
|
void routeChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isRouteExcluded(void* table, const IP_ADDRESS_PREFIX* dest) const;
|
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
|
||||||
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||||
const IP_ADDRESS_PREFIX* dest);
|
const IP_ADDRESS_PREFIX* dest);
|
||||||
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
||||||
@@ -47,7 +47,7 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
void updateCapturedRoutes(int family);
|
void updateCapturedRoutes(int family);
|
||||||
void updateCapturedRoutes(int family, void* table);
|
void updateCapturedRoutes(int family, void* table);
|
||||||
|
|
||||||
QSet<IPAddress> m_ownedExclusionRoutes;
|
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
||||||
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
||||||
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
||||||
|
|
||||||
@@ -57,8 +57,6 @@ class WindowsRouteMonitor final : public QObject {
|
|||||||
|
|
||||||
const quint64 m_luid = 0;
|
const quint64 m_luid = 0;
|
||||||
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
static QSet<quint64> s_vpnLuids;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* WINDOWSROUTEMONITOR_H */
|
#endif /* WINDOWSROUTEMONITOR_H */
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
#include "platforms/windows/windowsutils.h"
|
#include "platforms/windows/windowsutils.h"
|
||||||
#include "windowsdaemon.h"
|
#include "windowsdaemon.h"
|
||||||
|
|
||||||
#define TUNNEL_NAMED_PIPE_PREFIX \
|
#define TUNNEL_NAMED_PIPE \
|
||||||
"\\\\.\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\"
|
"\\\\." \
|
||||||
|
"\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\AmneziaVPN"
|
||||||
|
|
||||||
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
||||||
|
|
||||||
@@ -27,10 +28,6 @@ Logger logger("WindowsTunnelService");
|
|||||||
static bool stopAndDeleteTunnelService(SC_HANDLE service);
|
static bool stopAndDeleteTunnelService(SC_HANDLE service);
|
||||||
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
|
static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
|
||||||
|
|
||||||
std::wstring WindowsTunnelService::serviceNameForIfname(const QString& ifname) {
|
|
||||||
return (QStringLiteral("AmneziaWGTunnel$") + ifname).toStdWString();
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
||||||
MZ_COUNT_CTOR(WindowsTunnelService);
|
MZ_COUNT_CTOR(WindowsTunnelService);
|
||||||
logger.debug() << "WindowsTunnelService created.";
|
logger.debug() << "WindowsTunnelService created.";
|
||||||
@@ -40,7 +37,7 @@ WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
|||||||
WindowsUtils::windowsLog("Failed to open SCManager");
|
WindowsUtils::windowsLog("Failed to open SCManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the legacy single-tunnel service still around? Terminate it.
|
// Is the service already running? Terminate it.
|
||||||
SC_HANDLE service =
|
SC_HANDLE service =
|
||||||
OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||||
if (service != nullptr) {
|
if (service != nullptr) {
|
||||||
@@ -111,11 +108,8 @@ void WindowsTunnelService::timeout() {
|
|||||||
emit backendFailure();
|
emit backendFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsTunnelService::start(const QString& configData, const QString& ifname) {
|
bool WindowsTunnelService::start(const QString& configData) {
|
||||||
logger.debug() << "Starting the tunnel service for" << ifname;
|
logger.debug() << "Starting the tunnel service";
|
||||||
|
|
||||||
m_ifname = ifname;
|
|
||||||
const std::wstring serviceName = serviceNameForIfname(ifname);
|
|
||||||
|
|
||||||
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
|
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
|
||||||
m_logworker->moveToThread(&m_logthread);
|
m_logworker->moveToThread(&m_logthread);
|
||||||
@@ -134,9 +128,10 @@ bool WindowsTunnelService::start(const QString& configData, const QString& ifnam
|
|||||||
m_logworker = nullptr;
|
m_logworker = nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
service = OpenService(scm, serviceName.c_str(), SERVICE_ALL_ACCESS);
|
// Let's see if we have to delete a previous instance.
|
||||||
|
service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||||
if (service) {
|
if (service) {
|
||||||
logger.debug() << "A stale service was detected. Cleaning it up.";
|
logger.debug() << "An existing service has been detected. Let's close it.";
|
||||||
if (!stopAndDeleteTunnelService(service)) {
|
if (!stopAndDeleteTunnelService(service)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -148,12 +143,12 @@ bool WindowsTunnelService::start(const QString& configData, const QString& ifnam
|
|||||||
{
|
{
|
||||||
QTextStream out(&serviceCmdline);
|
QTextStream out(&serviceCmdline);
|
||||||
out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \""
|
out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \""
|
||||||
<< configData << "\" \"" << ifname << "\"";
|
<< configData << "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "Service:" << qApp->applicationFilePath();
|
logger.debug() << "Service:" << qApp->applicationFilePath();
|
||||||
|
|
||||||
service = CreateService(scm, serviceName.c_str(), L"Amnezia VPN (tunnel)",
|
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amnezia VPN (tunnel)",
|
||||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||||
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
|
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
|
||||||
@@ -241,9 +236,8 @@ static bool stopAndDeleteTunnelService(SC_HANDLE service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString WindowsTunnelService::uapiCommand(const QString& command) {
|
QString WindowsTunnelService::uapiCommand(const QString& command) {
|
||||||
const std::wstring pipeName = std::wstring(TEXT(TUNNEL_NAMED_PIPE_PREFIX))
|
// Create a pipe to the tunnel service.
|
||||||
+ m_ifname.toStdWString();
|
LPTSTR tunnelName = (LPTSTR)TEXT(TUNNEL_NAMED_PIPE);
|
||||||
LPCWSTR tunnelName = pipeName.c_str();
|
|
||||||
HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
HANDLE pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||||
OPEN_EXISTING, 0, nullptr);
|
OPEN_EXISTING, 0, nullptr);
|
||||||
if (pipe == INVALID_HANDLE_VALUE) {
|
if (pipe == INVALID_HANDLE_VALUE) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "windowstunnellogger.h"
|
#include "windowstunnellogger.h"
|
||||||
|
|
||||||
@@ -21,13 +20,11 @@ class WindowsTunnelService final : public QObject {
|
|||||||
WindowsTunnelService(QObject* parent = nullptr);
|
WindowsTunnelService(QObject* parent = nullptr);
|
||||||
~WindowsTunnelService();
|
~WindowsTunnelService();
|
||||||
|
|
||||||
bool start(const QString& configData, const QString& ifname);
|
bool start(const QString& configData);
|
||||||
void stop();
|
void stop();
|
||||||
bool isRunning();
|
bool isRunning();
|
||||||
QString uapiCommand(const QString& command);
|
QString uapiCommand(const QString& command);
|
||||||
|
|
||||||
static std::wstring serviceNameForIfname(const QString& ifname);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void backendFailure();
|
void backendFailure();
|
||||||
|
|
||||||
@@ -39,7 +36,6 @@ class WindowsTunnelService final : public QObject {
|
|||||||
QTimer m_timer;
|
QTimer m_timer;
|
||||||
QThread m_logthread;
|
QThread m_logthread;
|
||||||
WindowsTunnelLogger* m_logworker = nullptr;
|
WindowsTunnelLogger* m_logworker = nullptr;
|
||||||
QString m_ifname;
|
|
||||||
|
|
||||||
// These are really SC_HANDLEs in disguise.
|
// These are really SC_HANDLEs in disguise.
|
||||||
void* m_scm = nullptr;
|
void* m_scm = nullptr;
|
||||||
|
|||||||
@@ -102,36 +102,23 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't want to pass a peer just yet, that will happen later with
|
||||||
|
// a UAPI command in WireguardUtilsWindows::updatePeer(), so truncate
|
||||||
|
// the config file to remove the [Peer] section.
|
||||||
qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive);
|
qsizetype peerStart = configString.indexOf("[Peer]", 0, Qt::CaseSensitive);
|
||||||
if (peerStart >= 0) {
|
if (peerStart >= 0) {
|
||||||
configString.truncate(peerStart);
|
configString.truncate(peerStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stripLine = [&](const QString& key) {
|
if (!m_tunnel.start(configString)) {
|
||||||
qsizetype start = configString.startsWith(key + " = ")
|
|
||||||
? 0
|
|
||||||
: configString.indexOf("\n" + key + " = ");
|
|
||||||
if (start < 0) return;
|
|
||||||
if (start != 0) start += 1;
|
|
||||||
qsizetype end = configString.indexOf('\n', start);
|
|
||||||
if (end < 0) return;
|
|
||||||
configString.remove(start, end - start + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
stripLine("DNS");
|
|
||||||
if (config.m_deferAddressSetup) {
|
|
||||||
// Wintun rejects duplicate IPv4; daemon will assign at swap time.
|
|
||||||
stripLine("Address");
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ifname = config.m_ifname.isEmpty() ? s_defaultInterfaceName() : config.m_ifname;
|
|
||||||
if (!m_tunnel.start(configString, m_ifname)) {
|
|
||||||
logger.error() << "Failed to activate the tunnel service";
|
logger.error() << "Failed to activate the tunnel service";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the interface LUID
|
||||||
NET_LUID luid;
|
NET_LUID luid;
|
||||||
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)m_ifname.utf16(), &luid);
|
QString ifAlias = interfaceName();
|
||||||
|
DWORD result = ConvertInterfaceAliasToLuid((wchar_t*)ifAlias.utf16(), &luid);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
logger.error() << "Failed to lookup LUID:" << result;
|
logger.error() << "Failed to lookup LUID:" << result;
|
||||||
return false;
|
return false;
|
||||||
@@ -139,6 +126,14 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
|||||||
m_luid = luid.Value;
|
m_luid = luid.Value;
|
||||||
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
|
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
|
||||||
|
|
||||||
|
if (config.m_killSwitchEnabled) {
|
||||||
|
// Enable the windows firewall
|
||||||
|
NET_IFINDEX ifindex;
|
||||||
|
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||||
|
m_firewall->allowAllTraffic();
|
||||||
|
m_firewall->enableInterface(ifindex);
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug() << "Registration completed";
|
logger.debug() << "Registration completed";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -148,6 +143,7 @@ bool WireguardUtilsWindows::deleteInterface() {
|
|||||||
m_routeMonitor->deleteLater();
|
m_routeMonitor->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_firewall->disableKillSwitch();
|
||||||
m_tunnel.stop();
|
m_tunnel.stop();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -158,6 +154,10 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
|||||||
QByteArray pskKey =
|
QByteArray pskKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||||
|
|
||||||
|
if (config.m_killSwitchEnabled) {
|
||||||
|
// Enable the windows firewall for this peer.
|
||||||
|
m_firewall->enablePeerTraffic(config);
|
||||||
|
}
|
||||||
logger.debug() << "Configuring peer" << publicKey.toHex()
|
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||||
<< "via" << config.m_serverIpv4AddrIn;
|
<< "via" << config.m_serverIpv4AddrIn;
|
||||||
|
|
||||||
@@ -185,6 +185,12 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
|||||||
out << "allowed_ip=" << ip.toString() << "\n";
|
out << "allowed_ip=" << ip.toString() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude the server address, except for multihop exit servers.
|
||||||
|
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
|
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
QString reply = m_tunnel.uapiCommand(message);
|
QString reply = m_tunnel.uapiCommand(message);
|
||||||
logger.debug() << "DATA:" << reply;
|
logger.debug() << "DATA:" << reply;
|
||||||
return true;
|
return true;
|
||||||
@@ -194,6 +200,15 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
|
|||||||
QByteArray publicKey =
|
QByteArray publicKey =
|
||||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||||
|
|
||||||
|
// Clear exclustion routes for this peer.
|
||||||
|
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||||
|
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||||
|
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the windows firewall for this peer.
|
||||||
|
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
|
||||||
|
|
||||||
QString message;
|
QString message;
|
||||||
QTextStream out(&message);
|
QTextStream out(&message);
|
||||||
out << "set=1\n";
|
out << "set=1\n";
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
|||||||
~WireguardUtilsWindows();
|
~WireguardUtilsWindows();
|
||||||
|
|
||||||
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
||||||
QString interfaceName() override { return m_ifname; }
|
QString interfaceName() override {
|
||||||
static const QString s_defaultInterfaceName() { return "AmneziaVPN"; }
|
return WireguardUtilsWindows::s_interfaceName();
|
||||||
|
}
|
||||||
|
static const QString s_interfaceName() { return "AmneziaVPN"; }
|
||||||
bool addInterface(const InterfaceConfig& config) override;
|
bool addInterface(const InterfaceConfig& config) override;
|
||||||
bool deleteInterface() override;
|
bool deleteInterface() override;
|
||||||
|
|
||||||
@@ -52,7 +54,6 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
|||||||
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
||||||
|
|
||||||
quint64 m_luid = 0;
|
quint64 m_luid = 0;
|
||||||
QString m_ifname;
|
|
||||||
WindowsTunnelService m_tunnel;
|
WindowsTunnelService m_tunnel;
|
||||||
QPointer<WindowsRouteMonitor> m_routeMonitor;
|
QPointer<WindowsRouteMonitor> m_routeMonitor;
|
||||||
QPointer<WindowsFirewall> m_firewall;
|
QPointer<WindowsFirewall> m_firewall;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
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";\
|
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 || 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 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 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 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";\
|
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;\
|
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
|
||||||
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 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 pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
|
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
|
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
|
||||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
|
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
|
||||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
|
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
|
||||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
|
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
|
||||||
else pm="uname"; opt="-a";\
|
else pm="uname"; opt="-a";\
|
||||||
fi;\
|
fi;\
|
||||||
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
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";\
|
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 pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
||||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
||||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
|
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
|
||||||
elif pm=$(which 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";\
|
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";\
|
||||||
fi;\
|
else echo "Packet manager not found"; exit 1; 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 "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
|
||||||
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
|
|
||||||
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||||
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
|
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
|
||||||
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
|
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
|
||||||
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
|
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; 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 ! command -v docker > /dev/null 2>&1; then \
|
||||||
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
|
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||||
sudo -n $pm $check_pkgs;\
|
sleep 5; sudo systemctl enable --now docker; sleep 5;\
|
||||||
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
|
|
||||||
sudo -n $pm $silent_inst $docker_pkg;\
|
|
||||||
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
|
|
||||||
else \
|
|
||||||
echo "Container runtime is not supported";\
|
|
||||||
exit 1;\
|
|
||||||
fi;\
|
|
||||||
fi;\
|
fi;\
|
||||||
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
|
if [ "$(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 \
|
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
|
||||||
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
|
|
||||||
fi;\
|
|
||||||
fi;\
|
fi;\
|
||||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
|
if [ "$(systemctl is-active docker)" != "active" ]; then \
|
||||||
sleep 5; sudo -n systemctl start docker; sleep 5;\
|
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
|
sleep 5; sudo systemctl start docker; sleep 5;\
|
||||||
fi;\
|
fi;\
|
||||||
sudo -n docker --version || docker --version;\
|
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
||||||
|
docker --version;\
|
||||||
uname -sr
|
uname -sr
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||||
sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
|
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
||||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||||
sudo rm -frd /opt/amnezia
|
sudo rm -frd /opt/amnezia
|
||||||
|
|||||||
@@ -418,8 +418,7 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
|||||||
{
|
{
|
||||||
bool isConnectEvent = newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig;
|
bool isConnectEvent = newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig;
|
||||||
bool wasSubscriptionExpired = false;
|
bool wasSubscriptionExpired = false;
|
||||||
const auto oldApiV2 = m_serversController->apiV2Config(serverId);
|
if (const auto oldApiV2 = m_serversController->apiV2Config(serverId)) {
|
||||||
if (oldApiV2) {
|
|
||||||
wasSubscriptionExpired = oldApiV2->apiConfig.subscriptionExpiredByServer
|
wasSubscriptionExpired = oldApiV2->apiConfig.subscriptionExpiredByServer
|
||||||
|| oldApiV2->apiConfig.isSubscriptionExpired();
|
|| oldApiV2->apiConfig.isSubscriptionExpired();
|
||||||
}
|
}
|
||||||
@@ -427,10 +426,6 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
|||||||
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent);
|
ErrorCode errorCode = m_subscriptionController->updateServiceFromGateway(serverId, newCountryCode, isConnectEvent);
|
||||||
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
if (!newCountryCode.isEmpty() && oldApiV2) {
|
|
||||||
m_previousCountryServerId = serverId;
|
|
||||||
m_previousApiV2Config = oldApiV2;
|
|
||||||
}
|
|
||||||
if (wasSubscriptionExpired) {
|
if (wasSubscriptionExpired) {
|
||||||
emit subscriptionRefreshNeeded();
|
emit subscriptionRefreshNeeded();
|
||||||
}
|
}
|
||||||
@@ -452,20 +447,6 @@ bool SubscriptionUiController::updateServiceFromGateway(const QString &serverId,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SubscriptionUiController::revertLastCountryChange()
|
|
||||||
{
|
|
||||||
if (m_previousCountryServerId.isEmpty() || !m_previousApiV2Config) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QString serverId = m_previousCountryServerId;
|
|
||||||
const ApiV2ServerConfig cfg = *m_previousApiV2Config;
|
|
||||||
m_previousCountryServerId.clear();
|
|
||||||
m_previousApiV2Config.reset();
|
|
||||||
|
|
||||||
m_subscriptionController->restoreApiV2Config(serverId, cfg);
|
|
||||||
m_apiCountryModel->updateModel(cfg.apiConfig.availableCountries,
|
|
||||||
cfg.apiConfig.serverCountryCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SubscriptionUiController::deactivateDevice(const QString &serverId)
|
bool SubscriptionUiController::deactivateDevice(const QString &serverId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ public slots:
|
|||||||
bool importTrialFromGateway(const QString &email);
|
bool importTrialFromGateway(const QString &email);
|
||||||
bool updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, const QString &newCountryName,
|
bool updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, const QString &newCountryName,
|
||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
void revertLastCountryChange();
|
|
||||||
bool deactivateDevice(const QString &serverId);
|
bool deactivateDevice(const QString &serverId);
|
||||||
bool deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
bool deactivateExternalDevice(const QString &serverId, const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|
||||||
@@ -126,9 +125,6 @@ private:
|
|||||||
ApiDevicesModel* m_apiDevicesModel;
|
ApiDevicesModel* m_apiDevicesModel;
|
||||||
SettingsController* m_settingsController;
|
SettingsController* m_settingsController;
|
||||||
ConnectionController* m_connectionController;
|
ConnectionController* m_connectionController;
|
||||||
|
|
||||||
QString m_previousCountryServerId;
|
|
||||||
std::optional<ApiV2ServerConfig> m_previousApiV2Config;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SUBSCRIPTIONUICONTROLLER_H
|
#endif // SUBSCRIPTIONUICONTROLLER_H
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
|
|||||||
m_serversController(serversController)
|
m_serversController(serversController)
|
||||||
{
|
{
|
||||||
connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged);
|
connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged);
|
||||||
connect(m_connectionController, &ConnectionController::serverSwitchFailed, this, &ConnectionUiController::serverSwitchFailed);
|
|
||||||
|
|
||||||
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
|
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
|
||||||
|
|
||||||
@@ -66,12 +65,6 @@ void ConnectionUiController::onConnectionStateChanged(Vpn::ConnectionState state
|
|||||||
m_connectionStateText = tr("Connected");
|
m_connectionStateText = tr("Connected");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Vpn::ConnectionState::Switching: {
|
|
||||||
m_isConnectionInProgress = true;
|
|
||||||
m_isConnected = true;
|
|
||||||
m_connectionStateText = tr("Switching...");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Vpn::ConnectionState::Connecting: {
|
case Vpn::ConnectionState::Connecting: {
|
||||||
m_isConnectionInProgress = true;
|
m_isConnectionInProgress = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ signals:
|
|||||||
void prepareConfig();
|
void prepareConfig();
|
||||||
void unsupportedConnectDrawerRequested();
|
void unsupportedConnectDrawerRequested();
|
||||||
void noInstalledContainers();
|
void noInstalledContainers();
|
||||||
void serverSwitchFailed();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vpn::ConnectionState getCurrentConnectionState();
|
Vpn::ConnectionState getCurrentConnectionState();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ UpdateUiController::UpdateUiController(UpdateController* updateController, QObje
|
|||||||
{
|
{
|
||||||
if (m_updateController) {
|
if (m_updateController) {
|
||||||
connect(m_updateController, &UpdateController::updateFound, this, &UpdateUiController::updateFound);
|
connect(m_updateController, &UpdateController::updateFound, this, &UpdateUiController::updateFound);
|
||||||
|
connect(m_updateController, &UpdateController::installerVerificationFailed, this,
|
||||||
|
&UpdateUiController::installerVerificationFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateFound();
|
void updateFound();
|
||||||
|
void installerVerificationFailed(const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UpdateController* m_updateController;
|
UpdateController* m_updateController;
|
||||||
|
|||||||
@@ -74,22 +74,19 @@ ListViewType {
|
|||||||
: AmneziaStyle.color.mutedGray
|
: AmneziaStyle.color.mutedGray
|
||||||
|
|
||||||
checked: index === root.selectedIndex
|
checked: index === root.selectedIndex
|
||||||
checkable: !ConnectionController.isConnectionInProgress
|
checkable: !ConnectionController.isConnected
|
||||||
|
|
||||||
ButtonGroup.group: serversRadioButtonGroup
|
ButtonGroup.group: serversRadioButtonGroup
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (ConnectionController.isConnectionInProgress) {
|
if (ConnectionController.isConnected) {
|
||||||
PageController.showNotificationMessage(qsTr("Unable to change server while connection is in progress"))
|
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
root.selectedIndex = index
|
root.selectedIndex = index
|
||||||
ServersUiController.setDefaultServerAtIndex(index)
|
|
||||||
|
|
||||||
if (ConnectionController.isConnected) {
|
ServersUiController.setDefaultServerAtIndex(index)
|
||||||
ConnectionController.openConnection()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onEnterPressed: serverRadioButton.clicked()
|
Keys.onEnterPressed: serverRadioButton.clicked()
|
||||||
|
|||||||
@@ -36,10 +36,7 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
if (SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
|
||||||
&& ConnectionController.isConnected) {
|
|
||||||
ConnectionController.openConnection()
|
|
||||||
}
|
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,11 +202,15 @@ PageType {
|
|||||||
imageSource: "qrc:/images/controls/download.svg"
|
imageSource: "qrc:/images/controls/download.svg"
|
||||||
|
|
||||||
checked: index === ApiCountryModel.currentIndex
|
checked: index === ApiCountryModel.currentIndex
|
||||||
checkable: !ConnectionController.isConnectionInProgress
|
checkable: !ConnectionController.isConnected
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (ConnectionController.isConnectionInProgress) {
|
if (ConnectionController.isConnectionInProgress) {
|
||||||
PageController.showNotificationMessage(qsTr("Unable to change server location while connection is in progress"))
|
PageController.showNotificationMessage(qsTr("Unable change server location while trying to make an active connection"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (ConnectionController.isConnected) {
|
||||||
|
PageController.showNotificationMessage(qsTr("Unable change server location while there is an active connection"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,4 +454,12 @@ Window {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: UpdateController
|
||||||
|
function onInstallerVerificationFailed(message) {
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
PageController.showNotificationMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state)
|
|||||||
m_trayActionDisconnect->setEnabled(true);
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
break;
|
break;
|
||||||
case Vpn::ConnectionState::Connected:
|
case Vpn::ConnectionState::Connected:
|
||||||
case Vpn::ConnectionState::Switching:
|
|
||||||
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
||||||
m_trayActionConnect->setEnabled(false);
|
m_trayActionConnect->setEnabled(false);
|
||||||
m_trayActionDisconnect->setEnabled(true);
|
m_trayActionDisconnect->setEnabled(true);
|
||||||
|
|||||||
+211
-294
@@ -29,13 +29,14 @@
|
|||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "core/utils/constants/protocolConstants.h"
|
|
||||||
#include "core/utils/networkUtilities.h"
|
#include "core/utils/networkUtilities.h"
|
||||||
|
#include "core/utils/serverConfigUtils.h"
|
||||||
|
#include "vpnConnection.h"
|
||||||
|
|
||||||
using namespace ProtocolUtils;
|
using namespace ProtocolUtils;
|
||||||
|
|
||||||
VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
|
||||||
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this), m_trafficGuard(new VpnTrafficGuard(appSettingsRepository, this))
|
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this)
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
m_checkTimer.setInterval(1000);
|
m_checkTimer.setInterval(1000);
|
||||||
@@ -69,13 +70,112 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
|
|||||||
void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||||
{
|
{
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
switch (state) {
|
if (!m_serversRepository || !m_appSettingsRepository) {
|
||||||
case Vpn::ConnectionState::Connected: {
|
qCritical() << "VpnConnection::onConnectionStateChanged: repositories not initialized";
|
||||||
m_trafficGuard->setupRoutes(m_vpnConfiguration, vpnProtocol(), m_remoteAddress);
|
return;
|
||||||
} break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString defaultServerId = m_serversRepository->defaultServerId();
|
||||||
|
DockerContainer container = DockerContainer::None;
|
||||||
|
switch (m_serversRepository->serverKind(defaultServerId)) {
|
||||||
|
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||||
|
const auto cfg = m_serversRepository->selfHostedAdminConfig(defaultServerId);
|
||||||
|
if (cfg.has_value()) {
|
||||||
|
container = cfg->defaultContainer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||||
|
const auto cfg = m_serversRepository->selfHostedUserConfig(defaultServerId);
|
||||||
|
if (cfg.has_value()) {
|
||||||
|
container = cfg->defaultContainer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case serverConfigUtils::ConfigType::Native: {
|
||||||
|
const auto cfg = m_serversRepository->nativeConfig(defaultServerId);
|
||||||
|
if (cfg.has_value()) {
|
||||||
|
container = cfg->defaultContainer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||||
|
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||||
|
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||||
|
const auto cfg = m_serversRepository->apiV2Config(defaultServerId);
|
||||||
|
if (cfg.has_value()) {
|
||||||
|
container = cfg->defaultContainer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||||
|
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||||
|
break;
|
||||||
|
case serverConfigUtils::ConfigType::Invalid:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
switch (state) {
|
||||||
|
case Vpn::ConnectionState::Connected: {
|
||||||
|
iface->resetIpStack();
|
||||||
|
|
||||||
|
auto flushDns = iface->flushDns();
|
||||||
|
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||||
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
|
||||||
|
else
|
||||||
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
|
||||||
|
|
||||||
|
if (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard) {
|
||||||
|
QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString();
|
||||||
|
QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
if (!m_appSettingsRepository->isSitesSplitTunnelingEnabled() || m_appSettingsRepository->routeMode() != amnezia::RouteMode::VpnAllExceptSites) {
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (m_appSettingsRepository->isSitesSplitTunnelingEnabled()) {
|
||||||
|
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
|
||||||
|
RouteMode routeMode = m_appSettingsRepository->routeMode();
|
||||||
|
if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) {
|
||||||
|
QTimer::singleShot(1000, m_vpnProtocol.data(),
|
||||||
|
[this, routeMode]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), routeMode); });
|
||||||
|
} else if (routeMode == amnezia::RouteMode::VpnAllExceptSites) {
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
|
||||||
|
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
|
||||||
|
|
||||||
|
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << dns1 << dns2);
|
||||||
|
#endif
|
||||||
|
addSitesRoutes(m_vpnProtocol->routeGateway(), routeMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case Vpn::ConnectionState::Disconnected:
|
||||||
|
case Vpn::ConnectionState::Error: {
|
||||||
|
auto flushDns = iface->flushDns();
|
||||||
|
if (flushDns.waitForFinished() && flushDns.returnValue())
|
||||||
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
|
||||||
|
else
|
||||||
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
|
||||||
|
|
||||||
|
auto clearSavedRoutes = iface->clearSavedRoutes();
|
||||||
|
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
|
||||||
|
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
|
||||||
|
else
|
||||||
|
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
@@ -89,22 +189,82 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString &VpnConnection::remoteAddress() const
|
||||||
|
{
|
||||||
|
return m_remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository)
|
void VpnConnection::setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository)
|
||||||
{
|
{
|
||||||
m_serversRepository = serversRepository;
|
m_serversRepository = serversRepository;
|
||||||
m_appSettingsRepository = appSettingsRepository;
|
m_appSettingsRepository = appSettingsRepository;
|
||||||
m_trafficGuard.reset(new VpnTrafficGuard(appSettingsRepository, this));
|
}
|
||||||
|
|
||||||
|
void VpnConnection::addSitesRoutes(const QString &gw, amnezia::RouteMode mode)
|
||||||
|
{
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
if (!m_appSettingsRepository) {
|
||||||
|
qCritical() << "VpnConnection::addSitesRoutes: 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// re-resolve domains
|
||||||
|
for (const QString &site : sites) {
|
||||||
|
const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) {
|
||||||
|
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||||
|
QString ipv4Addr;
|
||||||
|
for (const QHostAddress &addr : hostInfo.addresses()) {
|
||||||
|
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||||
|
const QString &ip = addr.toString();
|
||||||
|
// qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip;
|
||||||
|
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() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
QHostInfo::lookupHost(site, this, cbResolv);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
|
QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
|
||||||
{
|
{
|
||||||
return m_active ? m_active->protocol() : m_vpnProtocol;
|
return m_vpnProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::disconnectSlots()
|
void VpnConnection::disconnectSlots()
|
||||||
{
|
{
|
||||||
if (auto proto = vpnProtocol()) {
|
if (m_vpnProtocol) {
|
||||||
proto->disconnect();
|
m_vpnProtocol->disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +274,11 @@ ErrorCode VpnConnection::lastError() const
|
|||||||
return ErrorCode::AndroidError;
|
return ErrorCode::AndroidError;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto proto = vpnProtocol();
|
if (m_vpnProtocol.isNull()) {
|
||||||
return proto.isNull() ? ErrorCode::InternalError : proto->lastError();
|
return ErrorCode::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_vpnProtocol.data()->lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vpn::ConnectionState VpnConnection::connectionState() const
|
Vpn::ConnectionState VpnConnection::connectionState() const
|
||||||
@@ -123,49 +286,6 @@ Vpn::ConnectionState VpnConnection::connectionState() const
|
|||||||
return m_connectionState;
|
return m_connectionState;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VpnConnection::allocateIfname()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
QString kernelAssigned;
|
|
||||||
IpcClient::withInterface([&kernelAssigned](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
||||||
auto reply = iface->reserveUtunName();
|
|
||||||
if (reply.waitForFinished(2000)) {
|
|
||||||
kernelAssigned = reply.returnValue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (kernelAssigned.isEmpty() || m_ifnamesInUse.contains(kernelAssigned)) {
|
|
||||||
qCritical() << "allocateIfname: kernel utun reservation failed";
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
m_ifnamesInUse.insert(kernelAssigned);
|
|
||||||
return kernelAssigned;
|
|
||||||
#else
|
|
||||||
for (int i = 0; ; ++i) {
|
|
||||||
const QString name = QStringLiteral("amn%1").arg(i);
|
|
||||||
if (!m_ifnamesInUse.contains(name)) {
|
|
||||||
m_ifnamesInUse.insert(name);
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::releaseIfname(const QString& ifname)
|
|
||||||
{
|
|
||||||
m_ifnamesInUse.remove(ifname);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::wireTunnelSignals(Tunnel* tunnel, bool isActive)
|
|
||||||
{
|
|
||||||
connect(tunnel, &Tunnel::prepared, this, &VpnConnection::onTunnelPrepared);
|
|
||||||
connect(tunnel, &Tunnel::activated, this, &VpnConnection::onTunnelActivated);
|
|
||||||
connect(tunnel, &Tunnel::failed, this, &VpnConnection::onTunnelFailed);
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
connect(tunnel, &Tunnel::bytesChanged, this, &VpnConnection::onBytesChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::connectToVpn(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration)
|
void VpnConnection::connectToVpn(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration)
|
||||||
{
|
{
|
||||||
if (!m_appSettingsRepository || !m_serversRepository) {
|
if (!m_appSettingsRepository || !m_serversRepository) {
|
||||||
@@ -179,88 +299,29 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
|
|||||||
.arg(ContainerUtils::containerToString(container))
|
.arg(ContainerUtils::containerToString(container))
|
||||||
<< m_appSettingsRepository->routeMode();
|
<< m_appSettingsRepository->routeMode();
|
||||||
|
|
||||||
const QString resolvedRemote =
|
m_remoteAddress = NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString());
|
||||||
NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString());
|
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
const bool isWg = VpnProtocol::isWireGuardBased(container);
|
|
||||||
const bool isXray = VpnProtocol::isXrayBased(container);
|
|
||||||
const bool targetIsSwitchable = isWg || isXray;
|
|
||||||
const bool activeIsSwitchable = m_active
|
|
||||||
&& (VpnProtocol::isWireGuardBased(m_active->container())
|
|
||||||
|| VpnProtocol::isXrayBased(m_active->container()));
|
|
||||||
const bool useTunnelPath = true;
|
|
||||||
const QString preAllocatedIfname = useTunnelPath ? allocateIfname() : QString();
|
|
||||||
if (useTunnelPath && preAllocatedIfname.isEmpty()) {
|
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
|
||||||
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_active
|
|
||||||
&& m_connectionState == Vpn::ConnectionState::Connected
|
|
||||||
&& targetIsSwitchable
|
|
||||||
&& activeIsSwitchable) {
|
|
||||||
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
|
|
||||||
releaseIfname(preAllocatedIfname);
|
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
|
||||||
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
startTunnelSwitch(container, vpnConfiguration, resolvedRemote, preAllocatedIfname);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
|
|
||||||
if (useTunnelPath) releaseIfname(preAllocatedIfname);
|
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
|
||||||
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
|
|
||||||
QJsonObject config = vpnConfiguration;
|
m_vpnConfiguration = vpnConfiguration;
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (m_active) {
|
if (m_vpnProtocol) {
|
||||||
const QString oldIfname = m_active->ifname();
|
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
m_trafficGuard->tearDown(m_active);
|
m_vpnProtocol->stop();
|
||||||
m_trafficGuard->flushAll();
|
m_vpnProtocol.reset();
|
||||||
delete m_active;
|
|
||||||
m_active = nullptr;
|
|
||||||
releaseIfname(oldIfname);
|
|
||||||
}
|
}
|
||||||
|
appendKillSwitchConfig();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
appendKillSwitchConfig(config);
|
appendSplitTunnelingConfig();
|
||||||
appendSplitTunnelingConfig(config);
|
|
||||||
|
|
||||||
m_vpnConfiguration = config;
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
m_remoteAddress = resolvedRemote;
|
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
|
||||||
|
if (!m_vpnProtocol) {
|
||||||
#ifdef AMNEZIA_DESKTOP
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
if (useTunnelPath) {
|
|
||||||
config.insert("ifname", preAllocatedIfname);
|
|
||||||
if (isXray) {
|
|
||||||
config.insert("tunName", preAllocatedIfname);
|
|
||||||
config.insert("deviceIpv4Address", amnezia::protocols::xray::defaultLocalAddr);
|
|
||||||
} else if (isWg) {
|
|
||||||
const QString protoName = config.value("protocol").toString();
|
|
||||||
const QJsonObject wgConfig = config.value(protoName + "_config_data").toObject();
|
|
||||||
const QString clientIp = wgConfig.value(amnezia::configKey::clientIp).toString();
|
|
||||||
if (!clientIp.isEmpty()) {
|
|
||||||
config.insert("deviceIpv4Address", clientIp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_vpnConfiguration = config;
|
|
||||||
m_active = new Tunnel(preAllocatedIfname, container, config, resolvedRemote, this);
|
|
||||||
wireTunnelSignals(m_active, /*isActive=*/true);
|
|
||||||
wireDaemonReconnectSignals();
|
|
||||||
m_trafficGuard->setConfig(config);
|
|
||||||
m_trafficGuard->bringUp(m_active);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_vpnProtocol->prepare();
|
||||||
#elif defined Q_OS_ANDROID
|
#elif defined Q_OS_ANDROID
|
||||||
androidVpnProtocol = createDefaultAndroidVpnProtocol();
|
androidVpnProtocol = createDefaultAndroidVpnProtocol();
|
||||||
createAndroidConnections();
|
createAndroidConnections();
|
||||||
@@ -286,42 +347,27 @@ void VpnConnection::createProtocolConnections()
|
|||||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||||
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
|
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
|
||||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||||
connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, this,
|
|
||||||
[this](const QString& gateway, const QString& localAddress) {
|
|
||||||
m_trafficGuard->applyKillSwitch(nullptr, gateway, localAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
wireDaemonReconnectSignals();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::wireDaemonReconnectSignals()
|
|
||||||
{
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
|
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
|
||||||
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn,
|
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
|
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
|
||||||
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn,
|
|
||||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
|
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::appendKillSwitchConfig(QJsonObject &config)
|
void VpnConnection::appendKillSwitchConfig()
|
||||||
{
|
{
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
if (!m_appSettingsRepository) {
|
if (!m_appSettingsRepository) {
|
||||||
qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized";
|
qCritical() << "VpnConnection::appendKillSwitchConfig: repositories not initialized";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString());
|
m_vpnConfiguration.insert(configKey::killSwitchOption, QVariant(m_appSettingsRepository->isKillSwitchEnabled()).toString());
|
||||||
config.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue());
|
m_vpnConfiguration.insert(configKey::allowedDnsServers, QVariant(m_appSettingsRepository->getAllowedDnsServers()).toJsonValue());
|
||||||
#else
|
|
||||||
Q_UNUSED(config)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
void VpnConnection::appendSplitTunnelingConfig()
|
||||||
{
|
{
|
||||||
if (!m_appSettingsRepository) {
|
if (!m_appSettingsRepository) {
|
||||||
qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized";
|
qCritical() << "VpnConnection::appendSplitTunnelingConfig: repositories not initialized";
|
||||||
@@ -331,14 +377,14 @@ void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
|||||||
bool allowSiteBasedSplitTunneling = true;
|
bool allowSiteBasedSplitTunneling = true;
|
||||||
|
|
||||||
// this block is for old native configs and for old self-hosted configs
|
// this block is for old native configs and for old self-hosted configs
|
||||||
auto protocolName = config.value(configKey::vpnProto).toString();
|
auto protocolName = m_vpnConfiguration.value(configKey::vpnProto).toString();
|
||||||
if (protocolName == ProtocolUtils::protoToString(Proto::Awg) || protocolName == ProtocolUtils::protoToString(Proto::WireGuard)) {
|
if (protocolName == ProtocolUtils::protoToString(Proto::Awg) || protocolName == ProtocolUtils::protoToString(Proto::WireGuard)) {
|
||||||
allowSiteBasedSplitTunneling = false;
|
allowSiteBasedSplitTunneling = false;
|
||||||
auto configData = config.value(protocolName + "_config_data").toObject();
|
auto configData = m_vpnConfiguration.value(protocolName + "_config_data").toObject();
|
||||||
if (configData.value(configKey::allowedIps).isString()) {
|
if (configData.value(configKey::allowedIps).isString()) {
|
||||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", "));
|
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configData.value(configKey::allowedIps).toString().split(", "));
|
||||||
configData.insert(configKey::allowedIps, allowedIpsJsonArray);
|
configData.insert(configKey::allowedIps, allowedIpsJsonArray);
|
||||||
config.insert(protocolName + "_config_data", configData);
|
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||||
} else if (configData.value(configKey::allowedIps).isUndefined()) {
|
} else if (configData.value(configKey::allowedIps).isUndefined()) {
|
||||||
auto nativeConfig = configData.value(configKey::config).toString();
|
auto nativeConfig = configData.value(configKey::config).toString();
|
||||||
auto nativeConfigLines = nativeConfig.split("\n");
|
auto nativeConfigLines = nativeConfig.split("\n");
|
||||||
@@ -350,7 +396,7 @@ void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
|||||||
}
|
}
|
||||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", "));
|
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(allowedIpsString.at(1).split(", "));
|
||||||
configData.insert(configKey::allowedIps, allowedIpsJsonArray);
|
configData.insert(configKey::allowedIps, allowedIpsJsonArray);
|
||||||
config.insert(protocolName + "_config_data", configData);
|
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +412,7 @@ void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1));
|
configData.insert(configKey::persistentKeepAlive, persistentKeepaliveString.at(1));
|
||||||
config.insert(protocolName + "_config_data", configData);
|
m_vpnConfiguration.insert(protocolName + "_config_data", configData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,14 +448,14 @@ void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
|||||||
routeMode = amnezia::RouteMode::VpnAllSites;
|
routeMode = amnezia::RouteMode::VpnAllSites;
|
||||||
} else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) {
|
} else if (routeMode == amnezia::RouteMode::VpnOnlyForwardSites) {
|
||||||
// Allow traffic to Amnezia DNS
|
// Allow traffic to Amnezia DNS
|
||||||
sitesJsonArray.append(config.value(configKey::dns1).toString());
|
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns1).toString());
|
||||||
sitesJsonArray.append(config.value(configKey::dns2).toString());
|
sitesJsonArray.append(m_vpnConfiguration.value(configKey::dns2).toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(configKey::splitTunnelType, routeMode);
|
m_vpnConfiguration.insert(configKey::splitTunnelType, routeMode);
|
||||||
config.insert(configKey::splitTunnelSites, sitesJsonArray);
|
m_vpnConfiguration.insert(configKey::splitTunnelSites, sitesJsonArray);
|
||||||
|
|
||||||
amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps;
|
amnezia::AppsRouteMode appsRouteMode = amnezia::AppsRouteMode::VpnAllApps;
|
||||||
QJsonArray appsJsonArray;
|
QJsonArray appsJsonArray;
|
||||||
@@ -426,8 +472,8 @@ void VpnConnection::appendSplitTunnelingConfig(QJsonObject &config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(configKey::appSplitTunnelType, appsRouteMode);
|
m_vpnConfiguration.insert(configKey::appSplitTunnelType, appsRouteMode);
|
||||||
config.insert(configKey::splitTunnelApps, appsJsonArray);
|
m_vpnConfiguration.insert(configKey::splitTunnelApps, appsJsonArray);
|
||||||
|
|
||||||
qDebug() << QString("Site split tunneling is %1, route mode is %2")
|
qDebug() << QString("Site split tunneling is %1, route mode is %2")
|
||||||
.arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
|
.arg(m_appSettingsRepository->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
|
||||||
@@ -469,6 +515,9 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::reconnectToVpn() {
|
void VpnConnection::reconnectToVpn() {
|
||||||
|
if (m_vpnProtocol.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
if (m_connectionState != Vpn::ConnectionState::Connected) {
|
if (m_connectionState != Vpn::ConnectionState::Connected) {
|
||||||
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
|
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
|
||||||
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
|
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
|
||||||
@@ -479,16 +528,6 @@ void VpnConnection::reconnectToVpn() {
|
|||||||
|
|
||||||
setConnectionState(Vpn::ConnectionState::Reconnecting);
|
setConnectionState(Vpn::ConnectionState::Reconnecting);
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
if (m_active) {
|
|
||||||
m_active->restart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (m_vpnProtocol.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_vpnProtocol->stop();
|
m_vpnProtocol->stop();
|
||||||
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
setConnectionState(Vpn::ConnectionState::Error);
|
||||||
@@ -504,26 +543,6 @@ void VpnConnection::disconnectFromVpn()
|
|||||||
disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
|
disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
if (m_staging) {
|
|
||||||
m_trafficGuard->tearDown(m_staging);
|
|
||||||
releaseIfname(m_staging->ifname());
|
|
||||||
delete m_staging;
|
|
||||||
m_staging = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_active) {
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
|
||||||
m_trafficGuard->tearDown(m_active);
|
|
||||||
m_trafficGuard->flushAll();
|
|
||||||
releaseIfname(m_active->ifname());
|
|
||||||
delete m_active;
|
|
||||||
m_active = nullptr;
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (m_vpnProtocol.isNull()) {
|
if (m_vpnProtocol.isNull()) {
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
return;
|
return;
|
||||||
@@ -542,9 +561,7 @@ void VpnConnection::disconnectFromVpn()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
#ifdef AMNEZIA_DESKTOP
|
|
||||||
m_trafficGuard->flushAll();
|
|
||||||
#endif
|
|
||||||
m_vpnProtocol->stop();
|
m_vpnProtocol->stop();
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
|
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
|
||||||
@@ -563,103 +580,3 @@ void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
|
|||||||
m_connectionState = state;
|
m_connectionState = state;
|
||||||
emit connectionStateChanged(state);
|
emit connectionStateChanged(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VpnConnection::startTunnelSwitch(DockerContainer container,
|
|
||||||
const QJsonObject &vpnConfiguration,
|
|
||||||
const QString &resolvedRemote,
|
|
||||||
const QString &stagingIfname)
|
|
||||||
{
|
|
||||||
QJsonObject config = vpnConfiguration;
|
|
||||||
config.insert("ifname", stagingIfname);
|
|
||||||
if (VpnProtocol::isXrayBased(container)) {
|
|
||||||
config.insert("tunName", stagingIfname);
|
|
||||||
config.insert("deviceIpv4Address", amnezia::protocols::xray::defaultLocalAddr);
|
|
||||||
} else if (VpnProtocol::isWireGuardBased(container)) {
|
|
||||||
const QString protoName = config.value("protocol").toString();
|
|
||||||
const QJsonObject wgConfig = config.value(protoName + "_config_data").toObject();
|
|
||||||
const QString clientIp = wgConfig.value(amnezia::configKey::clientIp).toString();
|
|
||||||
if (!clientIp.isEmpty()) {
|
|
||||||
config.insert("deviceIpv4Address", clientIp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appendKillSwitchConfig(config);
|
|
||||||
appendSplitTunnelingConfig(config);
|
|
||||||
|
|
||||||
m_staging = new Tunnel(stagingIfname, container, config, resolvedRemote, this);
|
|
||||||
if (m_active) {
|
|
||||||
m_staging->setHandoverIfname(m_active->ifname());
|
|
||||||
}
|
|
||||||
wireTunnelSignals(m_staging, /*isActive=*/false);
|
|
||||||
|
|
||||||
setConnectionState(Vpn::ConnectionState::Switching);
|
|
||||||
m_trafficGuard->bringUp(m_staging);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::onTunnelPrepared()
|
|
||||||
{
|
|
||||||
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
|
|
||||||
if (!tunnel) return;
|
|
||||||
|
|
||||||
if (tunnel == m_staging && m_active) {
|
|
||||||
Tunnel* oldTunnel = m_active;
|
|
||||||
const QString oldIfname = oldTunnel->ifname();
|
|
||||||
|
|
||||||
m_active = m_staging;
|
|
||||||
m_staging = nullptr;
|
|
||||||
connect(m_active, &Tunnel::bytesChanged, this, &VpnConnection::onBytesChanged);
|
|
||||||
m_vpnConfiguration = m_active->config();
|
|
||||||
m_remoteAddress = m_active->remoteAddress();
|
|
||||||
m_trafficGuard->setConfig(m_vpnConfiguration);
|
|
||||||
|
|
||||||
// Run the swap from a clean event-loop tick so the nested QEventLoop inside
|
|
||||||
// VpnTrafficGuard::swap does not deadlock the LSC.readData stack frame that
|
|
||||||
// delivered Tunnel::prepared.
|
|
||||||
QMetaObject::invokeMethod(this, [this, oldTunnel, oldIfname]() {
|
|
||||||
m_trafficGuard->swap(oldTunnel, m_active);
|
|
||||||
delete oldTunnel;
|
|
||||||
releaseIfname(oldIfname);
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_trafficGuard->commit(tunnel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::onTunnelActivated()
|
|
||||||
{
|
|
||||||
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
|
|
||||||
if (!tunnel) return;
|
|
||||||
|
|
||||||
if (tunnel == m_active) {
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConnection::onTunnelFailed(amnezia::ErrorCode error)
|
|
||||||
{
|
|
||||||
Tunnel* tunnel = qobject_cast<Tunnel*>(sender());
|
|
||||||
if (!tunnel) return;
|
|
||||||
|
|
||||||
if (tunnel == m_staging) {
|
|
||||||
m_trafficGuard->release(m_staging);
|
|
||||||
m_staging->deactivate();
|
|
||||||
releaseIfname(m_staging->ifname());
|
|
||||||
m_staging->deleteLater();
|
|
||||||
m_staging = nullptr;
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connected);
|
|
||||||
emit serverSwitchFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tunnel == m_active) {
|
|
||||||
m_trafficGuard->tearDown(m_active);
|
|
||||||
m_trafficGuard->flushAll();
|
|
||||||
releaseIfname(m_active->ifname());
|
|
||||||
m_active->deleteLater();
|
|
||||||
m_active = nullptr;
|
|
||||||
setConnectionState(Vpn::ConnectionState::Error);
|
|
||||||
if (error != ErrorCode::NoError) {
|
|
||||||
emit vpnProtocolError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-27
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QRemoteObjectNode>
|
#include <QRemoteObjectNode>
|
||||||
@@ -16,8 +15,9 @@
|
|||||||
#include "core/repositories/secureServersRepository.h"
|
#include "core/repositories/secureServersRepository.h"
|
||||||
#include "core/repositories/secureAppSettingsRepository.h"
|
#include "core/repositories/secureAppSettingsRepository.h"
|
||||||
|
|
||||||
#include "core/vpnTrafficGuard.h"
|
#ifdef AMNEZIA_DESKTOP
|
||||||
#include "core/tunnel.h"
|
#include "core/utils/ipcClient.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "core/protocols/androidVpnProtocol.h"
|
#include "core/protocols/androidVpnProtocol.h"
|
||||||
@@ -40,7 +40,8 @@ public:
|
|||||||
|
|
||||||
QSharedPointer<VpnProtocol> vpnProtocol() const;
|
QSharedPointer<VpnProtocol> vpnProtocol() const;
|
||||||
|
|
||||||
const QString &remoteAddress() const { return m_remoteAddress; }
|
const QString &remoteAddress() const;
|
||||||
|
void addSitesRoutes(const QString &gw, amnezia::RouteMode mode);
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
void restoreConnection();
|
void restoreConnection();
|
||||||
@@ -61,7 +62,6 @@ signals:
|
|||||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
void vpnProtocolError(amnezia::ErrorCode error);
|
void vpnProtocolError(amnezia::ErrorCode error);
|
||||||
void serverSwitchFailed();
|
|
||||||
|
|
||||||
void serviceIsNotReady();
|
void serviceIsNotReady();
|
||||||
|
|
||||||
@@ -75,15 +75,11 @@ protected:
|
|||||||
private:
|
private:
|
||||||
SecureServersRepository* m_serversRepository;
|
SecureServersRepository* m_serversRepository;
|
||||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||||
QScopedPointer<VpnTrafficGuard> m_trafficGuard;
|
|
||||||
|
|
||||||
QJsonObject m_vpnConfiguration;
|
QJsonObject m_vpnConfiguration;
|
||||||
|
QJsonObject m_routeMode;
|
||||||
QString m_remoteAddress;
|
QString m_remoteAddress;
|
||||||
|
|
||||||
Tunnel* m_active = nullptr;
|
|
||||||
Tunnel* m_staging = nullptr;
|
|
||||||
QSet<QString> m_ifnamesInUse;
|
|
||||||
|
|
||||||
// Only for iOS for now, check counters
|
// Only for iOS for now, check counters
|
||||||
QTimer m_checkTimer;
|
QTimer m_checkTimer;
|
||||||
|
|
||||||
@@ -97,24 +93,9 @@ private:
|
|||||||
Vpn::ConnectionState m_connectionState;
|
Vpn::ConnectionState m_connectionState;
|
||||||
|
|
||||||
void createProtocolConnections();
|
void createProtocolConnections();
|
||||||
void wireTunnelSignals(Tunnel* tunnel, bool isActive);
|
|
||||||
void wireDaemonReconnectSignals();
|
|
||||||
|
|
||||||
QString allocateIfname();
|
void appendSplitTunnelingConfig();
|
||||||
void releaseIfname(const QString& ifname);
|
void appendKillSwitchConfig();
|
||||||
|
|
||||||
void appendSplitTunnelingConfig(QJsonObject &config);
|
|
||||||
void appendKillSwitchConfig(QJsonObject &config);
|
|
||||||
|
|
||||||
void startTunnelSwitch(DockerContainer container,
|
|
||||||
const QJsonObject &vpnConfiguration,
|
|
||||||
const QString &resolvedRemote,
|
|
||||||
const QString &stagingIfname);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onTunnelPrepared();
|
|
||||||
void onTunnelActivated();
|
|
||||||
void onTunnelFailed(amnezia::ErrorCode error);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VPNCONNECTION_H
|
#endif // VPNCONNECTION_H
|
||||||
|
|||||||
+4
-4
@@ -19,12 +19,12 @@ class AmneziaVPN(ConanFile):
|
|||||||
|
|
||||||
if has_service:
|
if has_service:
|
||||||
if os == "Windows":
|
if os == "Windows":
|
||||||
self.requires("awg-windows/0.1.9")
|
self.requires("awg-windows/0.1.8")
|
||||||
self.requires("tap-windows6/9.27.0")
|
self.requires("tap-windows6/9.27.0")
|
||||||
self.requires("win-split-tunnel/1.2.5.0")
|
self.requires("win-split-tunnel/1.2.5.0")
|
||||||
self.requires("wintun/0.14.1")
|
self.requires("wintun/0.14.1")
|
||||||
else:
|
else:
|
||||||
self.requires("awg-go/0.2.18")
|
self.requires("awg-go/0.2.16")
|
||||||
|
|
||||||
self.requires("amnezia-xray-bindings/1.1.0")
|
self.requires("amnezia-xray-bindings/1.1.0")
|
||||||
self.requires("tun2socks/2.6.0")
|
self.requires("tun2socks/2.6.0")
|
||||||
@@ -32,13 +32,13 @@ class AmneziaVPN(ConanFile):
|
|||||||
self.requires("v2ray-rules-dat/202603162227")
|
self.requires("v2ray-rules-dat/202603162227")
|
||||||
|
|
||||||
if has_ne:
|
if has_ne:
|
||||||
self.requires("awg-apple/2.0.2")
|
self.requires("awg-apple/2.0.1")
|
||||||
self.requires("hev-socks5-tunnel/2.15.0", options={"as_framework": True})
|
self.requires("hev-socks5-tunnel/2.15.0", options={"as_framework": True})
|
||||||
self.requires("openvpnadapter/1.0.0")
|
self.requires("openvpnadapter/1.0.0")
|
||||||
|
|
||||||
if os == "Android":
|
if os == "Android":
|
||||||
self.requires("amnezia-libxray/1.0.0")
|
self.requires("amnezia-libxray/1.0.0")
|
||||||
self.requires("awg-android/2.0.1")
|
self.requires("awg-android/1.1.7")
|
||||||
self.requires("openvpn-pt-android/1.0.0")
|
self.requires("openvpn-pt-android/1.0.0")
|
||||||
|
|
||||||
# expicitly use libssh@amnezia to prevent it from being downloaded from conan-center
|
# expicitly use libssh@amnezia to prevent it from being downloaded from conan-center
|
||||||
|
|||||||
+3
-19
@@ -10,15 +10,8 @@ class IpcInterface
|
|||||||
|
|
||||||
// Route functions
|
// Route functions
|
||||||
SLOT( int routeAddList(const QString &gw, const QStringList &ips) );
|
SLOT( int routeAddList(const QString &gw, const QStringList &ips) );
|
||||||
SLOT( int routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips) );
|
|
||||||
SLOT( bool clearSavedRoutes() );
|
SLOT( bool clearSavedRoutes() );
|
||||||
SLOT( bool routeDeleteList(const QString &gw, const QStringList &ip) );
|
SLOT( bool routeDeleteList(const QString &gw, const QStringList &ip) );
|
||||||
SLOT( bool addExclusionRoute(const QString &ifname, const QString &addr) );
|
|
||||||
SLOT( bool delExclusionRoute(const QString &ifname, const QString &addr) );
|
|
||||||
SLOT( bool addAllowedIp(const QString &ifname, const QString &prefix) );
|
|
||||||
SLOT( bool delAllowedIp(const QString &ifname, const QString &prefix) );
|
|
||||||
SLOT( bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) );
|
|
||||||
SLOT( bool restoreTunnelResolvers() );
|
|
||||||
SLOT( bool flushDns() );
|
SLOT( bool flushDns() );
|
||||||
SLOT( void resetIpStack() );
|
SLOT( void resetIpStack() );
|
||||||
|
|
||||||
@@ -32,30 +25,21 @@ class IpcInterface
|
|||||||
SLOT( bool createTun(const QString &dev, const QString &subnet) );
|
SLOT( bool createTun(const QString &dev, const QString &subnet) );
|
||||||
SLOT( bool deleteTun(const QString &dev) );
|
SLOT( bool deleteTun(const QString &dev) );
|
||||||
|
|
||||||
SLOT( QString reserveUtunName() );
|
|
||||||
|
|
||||||
SLOT( bool applyAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6) );
|
|
||||||
|
|
||||||
SLOT( bool removeAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6) );
|
|
||||||
|
|
||||||
SLOT( bool StartRoutingIpv6() );
|
SLOT( bool StartRoutingIpv6() );
|
||||||
SLOT( bool StopRoutingIpv6() );
|
SLOT( bool StopRoutingIpv6() );
|
||||||
|
|
||||||
SLOT( bool disableKillSwitch() );
|
SLOT( bool disableKillSwitch() );
|
||||||
SLOT( bool disableKillSwitchForTunnel( const QString &ifname, const QStringList &remainingRanges ) );
|
|
||||||
SLOT( bool disableAllTraffic() );
|
SLOT( bool disableAllTraffic() );
|
||||||
SLOT( bool refreshKillSwitch( bool enabled ) );
|
SLOT( bool refreshKillSwitch( bool enabled ) );
|
||||||
SLOT( bool addKillSwitchAllowedRange( const QString &ifname, const QStringList ranges ) );
|
SLOT( bool addKillSwitchAllowedRange( const QStringList ranges ) );
|
||||||
SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) );
|
SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) );
|
||||||
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
|
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
|
||||||
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
|
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
|
||||||
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
|
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
|
||||||
SLOT( bool restoreResolvers() );
|
SLOT( bool restoreResolvers() );
|
||||||
|
|
||||||
SLOT(bool xrayStart(const QString &ifname, const QString &config));
|
SLOT(bool xrayStart(const QString &config));
|
||||||
SLOT(bool xrayStop(const QString &ifname));
|
SLOT(bool xrayStop());
|
||||||
SLOT(bool xrayAddUplinkRoutes(const QString &uplinkIface, const QString &uplinkGateway));
|
|
||||||
SLOT(bool xrayRemoveUplinkRoutes(const QString &uplinkIface, const QString &uplinkGateway));
|
|
||||||
|
|
||||||
SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) );
|
SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) );
|
||||||
SLOT( bool stopNetworkCheck() );
|
SLOT( bool stopNetworkCheck() );
|
||||||
|
|||||||
+10
-351
@@ -1,12 +1,9 @@
|
|||||||
#include "ipcserver.h"
|
#include "ipcserver.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
@@ -19,33 +16,12 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "router.h"
|
#include "router.h"
|
||||||
#include "killswitch.h"
|
#include "killswitch.h"
|
||||||
|
#include "xray.h"
|
||||||
#include "../client/daemon/daemon.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
#include "router_mac.h"
|
|
||||||
#include "core/utils/networkUtilities.h"
|
|
||||||
#include <QNetworkInterface>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <iphlpapi.h>
|
|
||||||
#include "tapcontroller_win.h"
|
#include "tapcontroller_win.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/sys_domain.h>
|
|
||||||
#include <sys/kern_control.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#include <net/if_utun.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cstring>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
|
IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
|
||||||
{
|
{
|
||||||
@@ -97,15 +73,6 @@ int IpcServer::routeAddList(const QString &gw, const QStringList &ips)
|
|||||||
return Router::routeAddList(gw, ips);
|
return Router::routeAddList(gw, ips);
|
||||||
}
|
}
|
||||||
|
|
||||||
int IpcServer::routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips)
|
|
||||||
{
|
|
||||||
#ifdef MZ_DEBUG
|
|
||||||
qDebug() << "IpcServer::routeAddListVia" << ifname;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return Router::routeAddListVia(ifname, gw, ips);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::clearSavedRoutes()
|
bool IpcServer::clearSavedRoutes()
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
@@ -124,36 +91,6 @@ bool IpcServer::routeDeleteList(const QString &gw, const QStringList &ips)
|
|||||||
return Router::routeDeleteList(gw, ips);
|
return Router::routeDeleteList(gw, ips);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::addExclusionRoute(const QString &ifname, const QString &addr)
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->addExclusionRoute(ifname, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::delExclusionRoute(const QString &ifname, const QString &addr)
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->delExclusionRoute(ifname, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::addAllowedIp(const QString &ifname, const QString &prefix)
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->addAllowedIp(ifname, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::delAllowedIp(const QString &ifname, const QString &prefix)
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->delAllowedIp(ifname, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::setTunnelResolvers(const QString &ifname, const QStringList &resolvers)
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->setTunnelResolvers(ifname, resolvers);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::restoreTunnelResolvers()
|
|
||||||
{
|
|
||||||
return Daemon::instance() && Daemon::instance()->restoreTunnelResolvers();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::flushDns()
|
bool IpcServer::flushDns()
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
@@ -235,126 +172,6 @@ bool IpcServer::deleteTun(const QString &dev)
|
|||||||
return Router::deleteTun(dev);
|
return Router::deleteTun(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString IpcServer::reserveUtunName()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
|
|
||||||
if (fd < 0) {
|
|
||||||
qWarning() << "reserveUtunName: socket() failed:" << strerror(errno);
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ctl_info info;
|
|
||||||
std::memset(&info, 0, sizeof(info));
|
|
||||||
std::strncpy(info.ctl_name, UTUN_CONTROL_NAME, sizeof(info.ctl_name) - 1);
|
|
||||||
if (ioctl(fd, CTLIOCGINFO, &info) < 0) {
|
|
||||||
qWarning() << "reserveUtunName: CTLIOCGINFO failed:" << strerror(errno);
|
|
||||||
::close(fd);
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_ctl addr;
|
|
||||||
std::memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sc_len = sizeof(addr);
|
|
||||||
addr.sc_family = AF_SYSTEM;
|
|
||||||
addr.ss_sysaddr = AF_SYS_CONTROL;
|
|
||||||
addr.sc_id = info.ctl_id;
|
|
||||||
addr.sc_unit = 0;
|
|
||||||
|
|
||||||
if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
|
|
||||||
qWarning() << "reserveUtunName: connect() failed:" << strerror(errno);
|
|
||||||
::close(fd);
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
char ifname[IFNAMSIZ] = {0};
|
|
||||||
socklen_t len = sizeof(ifname);
|
|
||||||
if (getsockopt(fd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, ifname, &len) < 0) {
|
|
||||||
qWarning() << "reserveUtunName: getsockopt UTUN_OPT_IFNAME failed:" << strerror(errno);
|
|
||||||
::close(fd);
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
::close(fd);
|
|
||||||
return QString::fromUtf8(ifname);
|
|
||||||
#else
|
|
||||||
return QString();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::applyAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
bool ok = true;
|
|
||||||
// Router::createTun on Windows assigns the address and blocks until it
|
|
||||||
// becomes live on the adapter (NotifyUnicastIpAddressChange callback).
|
|
||||||
if (!ipv4.isEmpty()) {
|
|
||||||
ok &= Router::createTun(ifname, ipv4);
|
|
||||||
}
|
|
||||||
if (!ipv6.isEmpty()) {
|
|
||||||
NET_LUID luid;
|
|
||||||
if (ConvertInterfaceAliasToLuid(reinterpret_cast<const wchar_t*>(ifname.utf16()), &luid) != NO_ERROR) {
|
|
||||||
qWarning() << "IpcServer::applyAdapterAddress: cannot resolve" << ifname;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const QByteArray ip = ipv6.section('/', 0, 0).toUtf8();
|
|
||||||
MIB_UNICASTIPADDRESS_ROW row;
|
|
||||||
InitializeUnicastIpAddressEntry(&row);
|
|
||||||
row.InterfaceLuid.Value = luid.Value;
|
|
||||||
row.Address.si_family = AF_INET6;
|
|
||||||
row.OnLinkPrefixLength = 128;
|
|
||||||
row.DadState = IpDadStatePreferred;
|
|
||||||
if (InetPtonA(AF_INET6, ip.toStdString().c_str(), &row.Address.Ipv6.sin6_addr) != 1) {
|
|
||||||
qWarning() << "IpcServer::applyAdapterAddress: cannot parse" << ipv6;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DWORD r = CreateUnicastIpAddressEntry(&row);
|
|
||||||
ok &= (r == NO_ERROR || r == ERROR_OBJECT_ALREADY_EXISTS);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
#else
|
|
||||||
Q_UNUSED(ifname)
|
|
||||||
Q_UNUSED(ipv4)
|
|
||||||
Q_UNUSED(ipv6)
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::removeAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
NET_LUID luid;
|
|
||||||
if (ConvertInterfaceAliasToLuid(reinterpret_cast<const wchar_t*>(ifname.utf16()), &luid) != NO_ERROR) {
|
|
||||||
qWarning() << "IpcServer::removeAdapterAddress: cannot resolve" << ifname;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto removeOne = [&](const QString& addr, int family) -> bool {
|
|
||||||
if (addr.isEmpty()) return true;
|
|
||||||
const QByteArray ip = addr.section('/', 0, 0).toUtf8();
|
|
||||||
MIB_UNICASTIPADDRESS_ROW row;
|
|
||||||
InitializeUnicastIpAddressEntry(&row);
|
|
||||||
row.InterfaceLuid.Value = luid.Value;
|
|
||||||
row.Address.si_family = static_cast<ADDRESS_FAMILY>(family);
|
|
||||||
void* dst = (family == AF_INET)
|
|
||||||
? static_cast<void*>(&row.Address.Ipv4.sin_addr)
|
|
||||||
: static_cast<void*>(&row.Address.Ipv6.sin6_addr);
|
|
||||||
if (InetPtonA(family, ip.toStdString().c_str(), dst) != 1) return false;
|
|
||||||
DWORD r = DeleteUnicastIpAddressEntry(&row);
|
|
||||||
return r == NO_ERROR || r == ERROR_NOT_FOUND;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ok = removeOne(ipv4, AF_INET);
|
|
||||||
ok &= removeOne(ipv6, AF_INET6);
|
|
||||||
return ok;
|
|
||||||
#else
|
|
||||||
Q_UNUSED(ifname)
|
|
||||||
Q_UNUSED(ipv4)
|
|
||||||
Q_UNUSED(ipv6)
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::updateResolvers(const QString &ifname, const QList<QHostAddress> &resolvers)
|
bool IpcServer::updateResolvers(const QString &ifname, const QList<QHostAddress> &resolvers)
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
@@ -370,9 +187,6 @@ bool IpcServer::restoreResolvers()
|
|||||||
qDebug() << "IpcServer::restoreResolvers";
|
qDebug() << "IpcServer::restoreResolvers";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_xrayWorkers.size() > 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return Router::restoreResolvers();
|
return Router::restoreResolvers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,13 +250,13 @@ bool IpcServer::resetKillSwitchAllowedRange(QStringList ranges)
|
|||||||
return KillSwitch::instance()->resetAllowedRange(ranges);
|
return KillSwitch::instance()->resetAllowedRange(ranges);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::addKillSwitchAllowedRange(const QString &ifname, QStringList ranges)
|
bool IpcServer::addKillSwitchAllowedRange(QStringList ranges)
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
qDebug() << "IpcServer::addKillSwitchAllowedRange" << ifname;
|
qDebug() << "IpcServer::addKillSwitchAllowedRange";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return KillSwitch::instance()->addAllowedRange(ifname, ranges);
|
return KillSwitch::instance()->addAllowedRange(ranges);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::disableAllTraffic()
|
bool IpcServer::disableAllTraffic()
|
||||||
@@ -472,15 +286,6 @@ bool IpcServer::disableKillSwitch()
|
|||||||
return KillSwitch::instance()->disableKillSwitch();
|
return KillSwitch::instance()->disableKillSwitch();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::disableKillSwitchForTunnel(const QString &ifname, const QStringList &remainingRanges)
|
|
||||||
{
|
|
||||||
#ifdef MZ_DEBUG
|
|
||||||
qDebug() << "IpcServer::disableKillSwitchForTunnel" << ifname;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return KillSwitch::instance()->disableKillSwitchForTunnel(ifname, remainingRanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
|
bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
@@ -499,166 +304,20 @@ bool IpcServer::refreshKillSwitch(bool enabled)
|
|||||||
return KillSwitch::instance()->refresh(enabled);
|
return KillSwitch::instance()->refresh(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcServer::onXrayWorkerLine(const QString& ifname, const QByteArray& line)
|
bool IpcServer::xrayStart(const QString& cfg)
|
||||||
{
|
|
||||||
const QJsonObject ev = QJsonDocument::fromJson(line).object();
|
|
||||||
const QString name = ev.value("ev").toString();
|
|
||||||
if (name == "log") {
|
|
||||||
const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
|
|
||||||
const QString level = ev.value("level").toString();
|
|
||||||
const QString msg = ev.value("msg").toString();
|
|
||||||
if (level == QLatin1String("warn")) {
|
|
||||||
qWarning().noquote() << prefix << msg;
|
|
||||||
} else if (level == QLatin1String("error") || level == QLatin1String("fatal")) {
|
|
||||||
qCritical().noquote() << prefix << msg;
|
|
||||||
} else if (level == QLatin1String("info")) {
|
|
||||||
qInfo().noquote() << prefix << msg;
|
|
||||||
} else {
|
|
||||||
qDebug().noquote() << prefix << msg;
|
|
||||||
}
|
|
||||||
} else if (name == "ready" || name == "failed") {
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
|
||||||
if (it != m_xrayWorkers.end() && it->startLoop) {
|
|
||||||
it->startResult = (name == "ready");
|
|
||||||
it->startLoop->quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::xrayStart(const QString& ifname, const QString& cfg)
|
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
qDebug() << "IpcServer::xrayStart" << ifname;
|
qDebug() << "IpcServer::xrayStart";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
XrayWorker& w = m_xrayWorkers[ifname];
|
return Xray::getInstance().startXray(cfg);
|
||||||
|
|
||||||
if (!w.process || w.process->state() == QProcess::NotRunning) {
|
|
||||||
w.process = QSharedPointer<QProcess>::create();
|
|
||||||
w.stdoutBuf.clear();
|
|
||||||
|
|
||||||
QObject::connect(w.process.data(), &QProcess::readyReadStandardOutput, this, [this, ifname]() {
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
|
||||||
if (it == m_xrayWorkers.end() || !it->process) return;
|
|
||||||
it->stdoutBuf.append(it->process->readAllStandardOutput());
|
|
||||||
int nl;
|
|
||||||
while ((nl = it->stdoutBuf.indexOf('\n')) >= 0) {
|
|
||||||
const QByteArray line = it->stdoutBuf.left(nl);
|
|
||||||
it->stdoutBuf.remove(0, nl + 1);
|
|
||||||
onXrayWorkerLine(ifname, line);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(w.process.data(), &QProcess::errorOccurred, this,
|
|
||||||
[this, ifname](QProcess::ProcessError err) {
|
|
||||||
const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
|
|
||||||
qCritical().noquote().nospace() << prefix << " process error: " << err;
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
|
||||||
if (it != m_xrayWorkers.end() && it->startLoop) {
|
|
||||||
it->startResult = false;
|
|
||||||
it->startLoop->quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(w.process.data(),
|
|
||||||
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
||||||
this, [this, ifname](int code, QProcess::ExitStatus status) {
|
|
||||||
const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
|
|
||||||
qDebug().noquote().nospace() << prefix << " finished, code=" << code << " status=" << status;
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
|
||||||
if (it != m_xrayWorkers.end() && it->startLoop) {
|
|
||||||
it->startResult = false;
|
|
||||||
it->startLoop->quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
w.process->setProgram(QCoreApplication::applicationFilePath());
|
|
||||||
w.process->setArguments({QStringLiteral("--xray-worker")});
|
|
||||||
w.process->start();
|
|
||||||
|
|
||||||
if (!w.process->waitForStarted(5000)) {
|
|
||||||
qCritical().noquote() << QStringLiteral("[xray-worker:%1] failed to start").arg(ifname);
|
|
||||||
m_xrayWorkers.remove(ifname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const QJsonObject startCmd{{QStringLiteral("op"), QStringLiteral("start")},
|
|
||||||
{QStringLiteral("config"), cfg}};
|
|
||||||
w.process->write(QJsonDocument(startCmd).toJson(QJsonDocument::Compact) + '\n');
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
w.startLoop = &loop;
|
|
||||||
w.startResult = false;
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
|
||||||
if (it == m_xrayWorkers.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
it->startLoop.clear();
|
|
||||||
const bool ok = it->startResult;
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
m_xrayWorkers.remove(ifname);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::xrayStop(const QString& ifname)
|
bool IpcServer::xrayStop()
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
qDebug() << "IpcServer::xrayStop" << ifname;
|
qDebug() << "IpcServer::xrayStop";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto it = m_xrayWorkers.find(ifname);
|
return Xray::getInstance().stopXray();
|
||||||
if (it == m_xrayWorkers.end()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it->process && it->process->state() != QProcess::NotRunning) {
|
|
||||||
const QJsonObject stopCmd{{QStringLiteral("op"), QStringLiteral("stop")}};
|
|
||||||
it->process->write(QJsonDocument(stopCmd).toJson(QJsonDocument::Compact) + '\n');
|
|
||||||
|
|
||||||
if (!it->process->waitForFinished(3000)) {
|
|
||||||
qWarning().noquote() << QStringLiteral("[xray-worker:%1] did not exit after stop, killing").arg(ifname);
|
|
||||||
it->process->kill();
|
|
||||||
it->process->waitForFinished(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_xrayWorkers.remove(ifname);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::xrayAddUplinkRoutes(const QString& uplinkIface, const QString& uplinkGateway)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
if (uplinkIface.isEmpty() || uplinkGateway.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return RouterMac::Instance().routeAddXray(uplinkIface, uplinkGateway);
|
|
||||||
#else
|
|
||||||
Q_UNUSED(uplinkIface)
|
|
||||||
Q_UNUSED(uplinkGateway)
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcServer::xrayRemoveUplinkRoutes(const QString& uplinkIface, const QString& uplinkGateway)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
if (uplinkIface.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_xrayWorkers.size() > 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return RouterMac::Instance().routeDeleteXray(uplinkIface, uplinkGateway);
|
|
||||||
#else
|
|
||||||
Q_UNUSED(uplinkIface)
|
|
||||||
Q_UNUSED(uplinkGateway)
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-32
@@ -1,14 +1,9 @@
|
|||||||
#ifndef IPCSERVER_H
|
#ifndef IPCSERVER_H
|
||||||
#define IPCSERVER_H
|
#define IPCSERVER_H
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QRemoteObjectNode>
|
#include <QRemoteObjectNode>
|
||||||
#include <QSharedPointer>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include "../client/daemon/interfaceconfig.h"
|
#include "../client/daemon/interfaceconfig.h"
|
||||||
#include "../client/mozilla/pinghelper.h"
|
#include "../client/mozilla/pinghelper.h"
|
||||||
@@ -22,19 +17,11 @@ class IpcServer : public IpcInterfaceSource
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IpcServer(QObject *parent = nullptr);
|
explicit IpcServer(QObject *parent = nullptr);
|
||||||
|
|
||||||
virtual int createPrivilegedProcess() override;
|
virtual int createPrivilegedProcess() override;
|
||||||
|
|
||||||
virtual int routeAddList(const QString &gw, const QStringList &ips) override;
|
virtual int routeAddList(const QString &gw, const QStringList &ips) override;
|
||||||
virtual int routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips) override;
|
|
||||||
virtual bool clearSavedRoutes() override;
|
virtual bool clearSavedRoutes() override;
|
||||||
virtual bool routeDeleteList(const QString &gw, const QStringList &ips) override;
|
virtual bool routeDeleteList(const QString &gw, const QStringList &ips) override;
|
||||||
virtual bool addExclusionRoute(const QString &ifname, const QString &addr) override;
|
|
||||||
virtual bool delExclusionRoute(const QString &ifname, const QString &addr) override;
|
|
||||||
virtual bool addAllowedIp(const QString &ifname, const QString &prefix) override;
|
|
||||||
virtual bool delAllowedIp(const QString &ifname, const QString &prefix) override;
|
|
||||||
virtual bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) override;
|
|
||||||
virtual bool restoreTunnelResolvers() override;
|
|
||||||
virtual bool flushDns() override;
|
virtual bool flushDns() override;
|
||||||
virtual void resetIpStack() override;
|
virtual void resetIpStack() override;
|
||||||
virtual bool checkAndInstallDriver() override;
|
virtual bool checkAndInstallDriver() override;
|
||||||
@@ -44,25 +31,19 @@ public:
|
|||||||
virtual void setLogsEnabled(bool enabled) override;
|
virtual void setLogsEnabled(bool enabled) override;
|
||||||
virtual bool createTun(const QString &dev, const QString &subnet) override;
|
virtual bool createTun(const QString &dev, const QString &subnet) override;
|
||||||
virtual bool deleteTun(const QString &dev) override;
|
virtual bool deleteTun(const QString &dev) override;
|
||||||
virtual QString reserveUtunName() override;
|
|
||||||
virtual bool applyAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6) override;
|
|
||||||
virtual bool removeAdapterAddress(const QString &ifname, const QString &ipv4, const QString &ipv6) override;
|
|
||||||
virtual bool StartRoutingIpv6() override;
|
virtual bool StartRoutingIpv6() override;
|
||||||
virtual bool StopRoutingIpv6() override;
|
virtual bool StopRoutingIpv6() override;
|
||||||
virtual bool disableAllTraffic() override;
|
virtual bool disableAllTraffic() override;
|
||||||
virtual bool addKillSwitchAllowedRange(const QString &ifname, QStringList ranges) override;
|
virtual bool addKillSwitchAllowedRange(QStringList ranges) override;
|
||||||
virtual bool resetKillSwitchAllowedRange(QStringList ranges) override;
|
virtual bool resetKillSwitchAllowedRange(QStringList ranges) override;
|
||||||
virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
|
virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
|
||||||
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
|
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
|
||||||
virtual bool disableKillSwitch() override;
|
virtual bool disableKillSwitch() override;
|
||||||
virtual bool disableKillSwitchForTunnel(const QString &ifname, const QStringList &remainingRanges) override;
|
|
||||||
virtual bool refreshKillSwitch( bool enabled ) override;
|
virtual bool refreshKillSwitch( bool enabled ) override;
|
||||||
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
|
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
|
||||||
virtual bool restoreResolvers() override;
|
virtual bool restoreResolvers() override;
|
||||||
virtual bool xrayStart(const QString& ifname, const QString& cfg) override;
|
virtual bool xrayStart(const QString& cfg) override;
|
||||||
virtual bool xrayStop(const QString& ifname) override;
|
virtual bool xrayStop() override;
|
||||||
virtual bool xrayAddUplinkRoutes(const QString& uplinkIface, const QString& uplinkGateway) override;
|
|
||||||
virtual bool xrayRemoveUplinkRoutes(const QString& uplinkIface, const QString& uplinkGateway) override;
|
|
||||||
virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override;
|
virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override;
|
||||||
virtual bool stopNetworkCheck() override;
|
virtual bool stopNetworkCheck() override;
|
||||||
|
|
||||||
@@ -83,16 +64,6 @@ private:
|
|||||||
|
|
||||||
QMap<int, ProcessDescriptor> m_processes;
|
QMap<int, ProcessDescriptor> m_processes;
|
||||||
PingHelper m_pingHelper;
|
PingHelper m_pingHelper;
|
||||||
|
|
||||||
struct XrayWorker {
|
|
||||||
QSharedPointer<QProcess> process;
|
|
||||||
QByteArray stdoutBuf;
|
|
||||||
QPointer<QEventLoop> startLoop;
|
|
||||||
bool startResult = false;
|
|
||||||
};
|
|
||||||
QHash<QString, XrayWorker> m_xrayWorkers;
|
|
||||||
|
|
||||||
void onXrayWorkerLine(const QString& ifname, const QByteArray& line);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPCSERVER_H
|
#endif // IPCSERVER_H
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import platform
|
|||||||
|
|
||||||
class AwgAndroid(ConanFile):
|
class AwgAndroid(ConanFile):
|
||||||
name = "awg-android"
|
name = "awg-android"
|
||||||
version = "2.0.1"
|
version = "1.1.7"
|
||||||
settings = "os", "arch", "build_type", "compiler"
|
settings = "os", "arch", "build_type", "compiler"
|
||||||
|
|
||||||
def configure(self):
|
def configure(self):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import os
|
|||||||
|
|
||||||
class AwgApple(ConanFile):
|
class AwgApple(ConanFile):
|
||||||
name = "awg-apple"
|
name = "awg-apple"
|
||||||
version = "2.0.2"
|
version = "2.0.1"
|
||||||
settings = "os", "arch", "compiler"
|
settings = "os", "arch", "compiler"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -39,7 +39,7 @@ class AwgApple(ConanFile):
|
|||||||
|
|
||||||
def source(self):
|
def source(self):
|
||||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-apple/archive/refs/tags/v{self.version}.zip",
|
get(self, f"https://github.com/amnezia-vpn/amneziawg-apple/archive/refs/tags/v{self.version}.zip",
|
||||||
sha256="a04f49eac9f82bbf5dd9031bab188d44de2b3482efde1b6e970821de1d5a3c5d", strip_root=True
|
sha256="9fe4f8cfbb6a751558b54b7979db3a5ea46e49731912aae99f093e84a1433e97", strip_root=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import os
|
|||||||
|
|
||||||
class AwgGo(ConanFile):
|
class AwgGo(ConanFile):
|
||||||
name = "awg-go"
|
name = "awg-go"
|
||||||
version = "0.2.18"
|
version = "0.2.16"
|
||||||
package_type = "application"
|
package_type = "application"
|
||||||
settings = "os", "arch"
|
settings = "os", "arch"
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class AwgGo(ConanFile):
|
|||||||
|
|
||||||
def source(self):
|
def source(self):
|
||||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-go/archive/refs/tags/v{self.version}.zip",
|
get(self, f"https://github.com/amnezia-vpn/amneziawg-go/archive/refs/tags/v{self.version}.zip",
|
||||||
sha256="58eefbd012e79bd1525f0e02d748979e9480acc1a339df8ceb3b9ffafcedb1ba", strip_root=True
|
sha256="34da7d4189f215f3930de441548bc2a0c89d54d347a4fb85cb9c715fce6413aa", strip_root=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import os
|
|||||||
|
|
||||||
class AwgWindows(ConanFile):
|
class AwgWindows(ConanFile):
|
||||||
name = "awg-windows"
|
name = "awg-windows"
|
||||||
version = "0.1.9"
|
version = "0.1.8"
|
||||||
settings = "os", "arch"
|
settings = "os", "arch"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -63,7 +63,7 @@ class AwgWindows(ConanFile):
|
|||||||
|
|
||||||
def source(self):
|
def source(self):
|
||||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-windows/archive/refs/tags/v{self.version}.zip",
|
get(self, f"https://github.com/amnezia-vpn/amneziawg-windows/archive/refs/tags/v{self.version}.zip",
|
||||||
sha256="5c29a75cb2beae291cc51b64840a39f838da5f300b9e956f7964813a687ec74c", strip_root=True)
|
sha256="1de472832b332515c96cdf14ea887edde42ed7ad173675280c51baa9a3ef62f2", strip_root=True)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
tc = AutotoolsToolchain(self)
|
tc = AutotoolsToolchain(self)
|
||||||
|
|||||||
@@ -650,9 +650,6 @@ class OpenSSLConan(ConanFile):
|
|||||||
if self._use_nmake:
|
if self._use_nmake:
|
||||||
self.cpp_info.components["ssl"].libs = ["libssl"]
|
self.cpp_info.components["ssl"].libs = ["libssl"]
|
||||||
self.cpp_info.components["crypto"].libs = ["libcrypto"]
|
self.cpp_info.components["crypto"].libs = ["libcrypto"]
|
||||||
elif self.settings.os == "Android" and self.options.shared:
|
|
||||||
self.cpp_info.components["ssl"].libs = ["ssl_3"]
|
|
||||||
self.cpp_info.components["crypto"].libs = ["crypto_3"]
|
|
||||||
else:
|
else:
|
||||||
self.cpp_info.components["ssl"].libs = ["ssl"]
|
self.cpp_info.components["ssl"].libs = ["ssl"]
|
||||||
self.cpp_info.components["crypto"].libs = ["crypto"]
|
self.cpp_info.components["crypto"].libs = ["crypto"]
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Openvpn(ConanFile):
|
|||||||
|
|
||||||
def build_requirements(self):
|
def build_requirements(self):
|
||||||
if self._is_windows:
|
if self._is_windows:
|
||||||
self.tool_requires("cmake/[>=4.2]")
|
self.tool_requires("cmake/[>=3.14 <4]")
|
||||||
else:
|
else:
|
||||||
self.tool_requires("libtool/2.4.7")
|
self.tool_requires("libtool/2.4.7")
|
||||||
self.tool_requires("automake/1.16.5")
|
self.tool_requires("automake/1.16.5")
|
||||||
|
|||||||
@@ -14,28 +14,6 @@
|
|||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include "../client/platforms/windows/daemon/windowsfirewall.h"
|
#include "../client/platforms/windows/daemon/windowsfirewall.h"
|
||||||
#include "../client/platforms/windows/daemon/windowsdaemon.h"
|
#include "../client/platforms/windows/daemon/windowsdaemon.h"
|
||||||
|
|
||||||
namespace {
|
|
||||||
int resolveVpnAdapterIndex(const QJsonObject& configStr) {
|
|
||||||
int index = configStr.value("vpnAdapterIndex").toInt();
|
|
||||||
if (index != 0) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
const QString ifname = configStr.value("ifname").toString();
|
|
||||||
if (ifname.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
NET_LUID luid;
|
|
||||||
if (ConvertInterfaceAliasToLuid((wchar_t*)ifname.utf16(), &luid) != 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
NET_IFINDEX ifindex = 0;
|
|
||||||
if (ConvertInterfaceLuidToIndex(&luid, &ifindex) != 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return static_cast<int>(ifindex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
@@ -159,16 +137,6 @@ bool KillSwitch::disableKillSwitch() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KillSwitch::disableKillSwitchForTunnel(const QString& ifname, const QStringList& remainingRanges) {
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
Q_UNUSED(remainingRanges)
|
|
||||||
return WindowsFirewall::create(this)->disableKillSwitchForTunnel(ifname);
|
|
||||||
#else
|
|
||||||
Q_UNUSED(ifname)
|
|
||||||
return resetAllowedRange(remainingRanges);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool KillSwitch::disableAllTraffic() {
|
bool KillSwitch::disableAllTraffic() {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
WindowsFirewall::create(this)->enableInterface(-1);
|
WindowsFirewall::create(this)->enableInterface(-1);
|
||||||
@@ -196,50 +164,31 @@ bool KillSwitch::disableAllTraffic() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList KillSwitch::combinedAllowNets() const {
|
|
||||||
QStringList result = m_allowedRanges;
|
|
||||||
for (const QString &site : m_splitTunnelAllows) {
|
|
||||||
if (!site.isEmpty() && !result.contains(site)) {
|
|
||||||
result.append(site);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
|
bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
|
||||||
|
|
||||||
m_allowedRanges = ranges;
|
m_allowedRanges = ranges;
|
||||||
const QStringList combined = combinedAllowNets();
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true);
|
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true);
|
||||||
LinuxFirewall::updateAllowNets(combined);
|
LinuxFirewall::updateAllowNets(m_allowedRanges);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), true);
|
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), true);
|
||||||
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), true, QStringLiteral("allownets"), combined);
|
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), true, QStringLiteral("allownets"), m_allowedRanges);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (isStrictKillSwitchEnabled()) {
|
if (isStrictKillSwitchEnabled()) {
|
||||||
WindowsFirewall::create(this)->enableInterface(-1);
|
WindowsFirewall::create(this)->enableInterface(-1);
|
||||||
}
|
}
|
||||||
WindowsFirewall::create(this)->allowTrafficRange(combined);
|
WindowsFirewall::create(this)->allowTrafficRange(m_allowedRanges);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KillSwitch::addAllowedRange(const QString &ifname, const QStringList &ranges) {
|
bool KillSwitch::addAllowedRange(const QStringList &ranges) {
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
if (!ifname.isEmpty()) {
|
|
||||||
return WindowsFirewall::create(this)->allowTrafficRange(ranges, ifname);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
Q_UNUSED(ifname)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (const QString &range : ranges) {
|
for (const QString &range : ranges) {
|
||||||
if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
|
if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
|
||||||
m_allowedRanges.append(range);
|
m_allowedRanges.append(range);
|
||||||
@@ -260,14 +209,10 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) {
|
|||||||
config.m_secondaryDnsServer = configStr.value(amnezia::configKey::dns2).toString();
|
config.m_secondaryDnsServer = configStr.value(amnezia::configKey::dns2).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
config.m_ifname = configStr.value("ifname").toString();
|
config.m_serverPublicKey = "openvpn";
|
||||||
const QString protocolName = configStr.value(amnezia::configKey::vpnProto).toString();
|
|
||||||
const QString pubkey = configStr.value(protocolName + "_config_data").toObject()
|
|
||||||
.value(amnezia::configKey::serverPubKey).toString();
|
|
||||||
config.m_serverPublicKey = pubkey.isEmpty() ? QStringLiteral("openvpn") : pubkey;
|
|
||||||
config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString();
|
config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString();
|
||||||
config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString();
|
config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString();
|
||||||
int vpnAdapterIndex = resolveVpnAdapterIndex(configStr);
|
int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt();
|
||||||
int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt();
|
int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt();
|
||||||
|
|
||||||
int splitTunnelType = configStr.value("splitTunnelType").toInt();
|
int splitTunnelType = configStr.value("splitTunnelType").toInt();
|
||||||
@@ -326,13 +271,10 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) {
|
|||||||
|
|
||||||
bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) {
|
bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
Q_UNUSED(vpnAdapterIndex)
|
|
||||||
const int resolvedIndex = resolveVpnAdapterIndex(configStr);
|
|
||||||
const QString ifname = configStr.value("ifname").toString();
|
|
||||||
if (configStr.value("splitTunnelType").toInt() != 0) {
|
if (configStr.value("splitTunnelType").toInt() != 0) {
|
||||||
WindowsFirewall::create(this)->allowAllTraffic();
|
WindowsFirewall::create(this)->allowAllTraffic();
|
||||||
}
|
}
|
||||||
return WindowsFirewall::create(this)->enableInterface(resolvedIndex, ifname);
|
return WindowsFirewall::create(this)->enableInterface(vpnAdapterIndex);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
@@ -364,13 +306,6 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
|
|||||||
allownets.append(v.toString());
|
allownets.append(v.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_splitTunnelAllows = allownets;
|
|
||||||
for (const QString &endpoint : m_allowedRanges) {
|
|
||||||
if (!endpoint.isEmpty() && !allownets.contains(endpoint)) {
|
|
||||||
allownets.append(endpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
|
|||||||
@@ -14,19 +14,16 @@ public:
|
|||||||
bool init();
|
bool init();
|
||||||
bool refresh(bool enabled);
|
bool refresh(bool enabled);
|
||||||
bool disableKillSwitch();
|
bool disableKillSwitch();
|
||||||
bool disableKillSwitchForTunnel(const QString& ifname, const QStringList& remainingRanges);
|
|
||||||
bool disableAllTraffic();
|
bool disableAllTraffic();
|
||||||
bool enablePeerTraffic(const QJsonObject &configStr);
|
bool enablePeerTraffic(const QJsonObject &configStr);
|
||||||
bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex);
|
bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex);
|
||||||
bool resetAllowedRange(const QStringList &ranges);
|
bool resetAllowedRange(const QStringList &ranges);
|
||||||
bool addAllowedRange(const QString &ifname, const QStringList &ranges);
|
bool addAllowedRange(const QStringList &ranges);
|
||||||
bool isStrictKillSwitchEnabled();
|
bool isStrictKillSwitchEnabled();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KillSwitch(QObject* parent) {};
|
KillSwitch(QObject* parent) {};
|
||||||
QStringList combinedAllowNets() const;
|
|
||||||
QStringList m_allowedRanges;
|
QStringList m_allowedRanges;
|
||||||
QStringList m_splitTunnelAllows;
|
|
||||||
QSharedPointer<SecureQSettings> m_appSettigns;
|
QSharedPointer<SecureQSettings> m_appSettigns;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,20 +56,14 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
|||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
// Signal handling for a proper shutdown.
|
// Signal handling for a proper shutdown.
|
||||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() {
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
LinuxDaemon::instance()->deactivate();
|
[]() { LinuxDaemon::instance()->deactivate(); });
|
||||||
m_ipcServer.restoreTunnelResolvers();
|
|
||||||
m_ipcServer.disableKillSwitch();
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
// Signal handling for a proper shutdown.
|
// Signal handling for a proper shutdown.
|
||||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() {
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
MacOSDaemon::instance()->deactivate();
|
[]() { MacOSDaemon::instance()->deactivate(); });
|
||||||
m_ipcServer.restoreTunnelResolvers();
|
|
||||||
m_ipcServer.disableKillSwitch();
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|||||||
@@ -1,139 +1,21 @@
|
|||||||
#include <QCoreApplication>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "localserver.h"
|
#include "localserver.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "systemservice.h"
|
#include "systemservice.h"
|
||||||
#include "xray.h"
|
|
||||||
#include "core/utils/utilities.h"
|
#include "core/utils/utilities.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int s_argc = 0;
|
int s_argc = 0;
|
||||||
char** s_argv = nullptr;
|
char** s_argv = nullptr;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr const char* kXrayWorkerArg = "--xray-worker";
|
|
||||||
|
|
||||||
void writeWorkerEvent(const QJsonObject& obj)
|
|
||||||
{
|
|
||||||
const QByteArray bytes = QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
|
|
||||||
std::fwrite(bytes.constData(), 1, bytes.size(), stdout);
|
|
||||||
std::fflush(stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void workerMessageHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
|
|
||||||
{
|
|
||||||
const char* level = "debug";
|
|
||||||
switch (type) {
|
|
||||||
case QtDebugMsg: level = "debug"; break;
|
|
||||||
case QtInfoMsg: level = "info"; break;
|
|
||||||
case QtWarningMsg: level = "warn"; break;
|
|
||||||
case QtCriticalMsg: level = "error"; break;
|
|
||||||
case QtFatalMsg: level = "fatal"; break;
|
|
||||||
}
|
|
||||||
writeWorkerEvent({{"ev", "log"}, {"level", QString::fromLatin1(level)}, {"msg", msg}});
|
|
||||||
}
|
|
||||||
|
|
||||||
int readStdinChunk(char* buf, int cap)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
DWORD n = 0;
|
|
||||||
BOOL ok = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf, cap, &n, nullptr);
|
|
||||||
return ok ? static_cast<int>(n) : -1;
|
|
||||||
#else
|
|
||||||
return static_cast<int>(::read(STDIN_FILENO, buf, cap));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int runXrayWorker(int argc, char** argv)
|
|
||||||
{
|
|
||||||
qInstallMessageHandler(workerMessageHandler);
|
|
||||||
QCoreApplication app(argc, argv);
|
|
||||||
|
|
||||||
auto* xray = new Xray;
|
|
||||||
auto* buf = new QByteArray;
|
|
||||||
|
|
||||||
auto exitWorker = [xray](int code) {
|
|
||||||
xray->stopXray();
|
|
||||||
std::fflush(stdout);
|
|
||||||
std::_Exit(code);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto handleLine = [xray, exitWorker](const QByteArray& line) {
|
|
||||||
const QJsonObject cmd = QJsonDocument::fromJson(line).object();
|
|
||||||
const QString op = cmd.value("op").toString();
|
|
||||||
if (op == "start") {
|
|
||||||
const QString cfg = cmd.value("config").toString();
|
|
||||||
const bool ok = xray->startXray(cfg);
|
|
||||||
writeWorkerEvent({{"ev", ok ? "ready" : "failed"}});
|
|
||||||
} else if (op == "stop") {
|
|
||||||
writeWorkerEvent({{"ev", "stopped"}});
|
|
||||||
exitWorker(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto onChunk = [buf, handleLine](const QByteArray& data) {
|
|
||||||
buf->append(data);
|
|
||||||
int nl;
|
|
||||||
while ((nl = buf->indexOf('\n')) >= 0) {
|
|
||||||
const QByteArray line = buf->left(nl);
|
|
||||||
buf->remove(0, nl + 1);
|
|
||||||
handleLine(line);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Detached reader thread: stdin is an anonymous pipe (the parent's write end).
|
|
||||||
// QSocketNotifier doesn't work on anonymous pipes on Windows, so use a blocking
|
|
||||||
// read in a thread and dispatch events back to the main thread. On EOF the
|
|
||||||
// worker exits immediately via _Exit to avoid races with QCoreApplication
|
|
||||||
// teardown while this thread is still alive.
|
|
||||||
std::thread([onChunk, exitWorker]() {
|
|
||||||
char chunk[4096];
|
|
||||||
while (true) {
|
|
||||||
const int n = readStdinChunk(chunk, sizeof(chunk));
|
|
||||||
if (n <= 0) {
|
|
||||||
// Parent gone or pipe error: shut xray down and exit.
|
|
||||||
exitWorker(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QMetaObject::invokeMethod(qApp, [onChunk, data = QByteArray(chunk, n)]() {
|
|
||||||
onChunk(data);
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
}).detach();
|
|
||||||
|
|
||||||
return app.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasXrayWorkerArg(int argc, char** argv)
|
|
||||||
{
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
if (std::strcmp(argv[i], kXrayWorkerArg) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int runApplication(int argc, char** argv)
|
int runApplication(int argc, char** argv)
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc,argv);
|
QCoreApplication app(argc,argv);
|
||||||
@@ -162,10 +44,6 @@ int runApplication(int argc, char** argv)
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (hasXrayWorkerArg(argc, argv)) {
|
|
||||||
return runXrayWorker(argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::initializePath(Logger::systemLogDir());
|
Utils::initializePath(Logger::systemLogDir());
|
||||||
|
|
||||||
if (argc >= 2) {
|
if (argc >= 2) {
|
||||||
|
|||||||
@@ -20,16 +20,6 @@ int Router::routeAddList(const QString &gw, const QStringList &ips)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int Router::routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
return RouterLinux::Instance().routeAddListVia(ifname, gw, ips);
|
|
||||||
#else
|
|
||||||
Q_UNUSED(ifname)
|
|
||||||
return routeAddList(gw, ips);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Router::clearSavedRoutes()
|
bool Router::clearSavedRoutes()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class Router : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static int routeAddList(const QString &gw, const QStringList &ips);
|
static int routeAddList(const QString &gw, const QStringList &ips);
|
||||||
static int routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips);
|
|
||||||
static bool clearSavedRoutes();
|
static bool clearSavedRoutes();
|
||||||
static int routeDeleteList(const QString &gw, const QStringList &ips);
|
static int routeDeleteList(const QString &gw, const QStringList &ips);
|
||||||
static bool flushDns();
|
static bool flushDns();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <core/utils/utilities.h>
|
#include <core/utils/utilities.h>
|
||||||
#include <cerrno>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
@@ -58,20 +57,15 @@ bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const
|
|||||||
route.rt_flags = RTF_UP | RTF_GATEWAY;
|
route.rt_flags = RTF_UP | RTF_GATEWAY;
|
||||||
route.rt_metric = 0;
|
route.rt_metric = 0;
|
||||||
|
|
||||||
if (ioctl(sock, SIOCADDRT, &route) < 0 && errno != EEXIST)
|
if (int err = ioctl(sock, SIOCADDRT, &route) < 0)
|
||||||
{
|
{
|
||||||
qDebug().noquote() << "route add error: gw " << gw << " ip " << ip
|
qDebug().noquote() << "route add error: gw "
|
||||||
<< " mask " << mask << " errno " << errno;
|
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
|
||||||
|
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
|
||||||
|
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EEXIST means the route is already in the kernel table (e.g. left over from a
|
|
||||||
// prior session). We still want to track it so it gets cleaned up on teardown.
|
|
||||||
for (const Route &r : m_addedRoutes) {
|
|
||||||
if (r.dst == ipWithSubnet && r.gw == gw) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_addedRoutes.append({ipWithSubnet, gw});
|
m_addedRoutes.append({ipWithSubnet, gw});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -87,34 +81,6 @@ int RouterLinux::routeAddList(const QString &gw, const QStringList &ips)
|
|||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RouterLinux::routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips)
|
|
||||||
{
|
|
||||||
if (ifname.isEmpty()) {
|
|
||||||
qWarning() << "routeAddListVia: empty ifname";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int cnt = 0;
|
|
||||||
for (const QString &ip : ips) {
|
|
||||||
QStringList args;
|
|
||||||
args << "route" << "replace" << ip;
|
|
||||||
if (!gw.isEmpty()) {
|
|
||||||
args << "via" << gw;
|
|
||||||
}
|
|
||||||
args << "dev" << ifname << "scope" << "link";
|
|
||||||
|
|
||||||
QProcess p;
|
|
||||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
|
||||||
p.start("ip", args);
|
|
||||||
if (p.waitForFinished(2000) && p.exitCode() == 0) {
|
|
||||||
cnt++;
|
|
||||||
} else {
|
|
||||||
qWarning().noquote() << "routeAddListVia failed:" << ip << "via" << gw << "dev" << ifname
|
|
||||||
<< "rc=" << p.exitCode() << "out=" << p.readAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RouterLinux::clearSavedRoutes()
|
bool RouterLinux::clearSavedRoutes()
|
||||||
{
|
{
|
||||||
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
@@ -169,13 +135,7 @@ bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, co
|
|||||||
|
|
||||||
if (ioctl(sock, SIOCDELRT, &route) < 0)
|
if (ioctl(sock, SIOCDELRT, &route) < 0)
|
||||||
{
|
{
|
||||||
// ESRCH means the route is already gone (e.g. kernel auto-removed it when
|
qDebug().noquote() << "route delete error: gw " << gw << " ip " << ip << " mask " << mask;
|
||||||
// the owning interface went away). The delete is a no-op, not a failure.
|
|
||||||
if (errno == ESRCH) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
qDebug().noquote() << "route delete error: gw " << gw << " ip " << ip
|
|
||||||
<< " mask " << mask << " errno " << errno;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ public:
|
|||||||
|
|
||||||
bool routeAdd(const QString &ip, const QString &gw, const int &sock);
|
bool routeAdd(const QString &ip, const QString &gw, const int &sock);
|
||||||
int routeAddList(const QString &gw, const QStringList &ips);
|
int routeAddList(const QString &gw, const QStringList &ips);
|
||||||
int routeAddListVia(const QString &ifname, const QString &gw, const QStringList &ips);
|
|
||||||
bool clearSavedRoutes();
|
bool clearSavedRoutes();
|
||||||
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
|
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
|
||||||
bool routeDeleteList(const QString &gw, const QStringList &ips);
|
bool routeDeleteList(const QString &gw, const QStringList &ips);
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ bool RouterMac::routeAdd(const QString &ipWithSubnet, const QString &gw)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
routeDelete(ipWithSubnet, gw);
|
|
||||||
|
|
||||||
QString cmd;
|
QString cmd;
|
||||||
if (mask == "255.255.255.255") {
|
if (mask == "255.255.255.255") {
|
||||||
cmd = QString("route add -host %1 %2").arg(ip).arg(gw);
|
cmd = QString("route add -host %1 %2").arg(ip).arg(gw);
|
||||||
|
|||||||
+25
-1
@@ -1,5 +1,8 @@
|
|||||||
#include "xray.h"
|
#include "xray.h"
|
||||||
#include "core/utils/networkUtilities.h"
|
#include "core/utils/networkUtilities.h"
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
#include "router_mac.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
@@ -31,7 +34,9 @@
|
|||||||
bool Xray::startXray(const QString &cfg)
|
bool Xray::startXray(const QString &cfg)
|
||||||
{
|
{
|
||||||
qDebug() << "Xray::startXray()";
|
qDebug() << "Xray::startXray()";
|
||||||
const QNetworkInterface defaultIface = NetworkUtilities::getGatewayAndIface().second;
|
const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface();
|
||||||
|
const QString defaultGateway = gatewayAndIface.first;
|
||||||
|
const QNetworkInterface defaultIface = gatewayAndIface.second;
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
m_defaultIfaceName = defaultIface.name().toUtf8();
|
m_defaultIfaceName = defaultIface.name().toUtf8();
|
||||||
#else
|
#else
|
||||||
@@ -41,6 +46,17 @@ bool Xray::startXray(const QString &cfg)
|
|||||||
qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")";
|
qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
m_uplinkIfaceName = defaultIface.name();
|
||||||
|
m_uplinkGateway = defaultGateway;
|
||||||
|
if (!m_uplinkIfaceName.isEmpty()) {
|
||||||
|
const bool installed = RouterMac::Instance().routeAddXray(m_uplinkIfaceName, m_uplinkGateway);
|
||||||
|
if (!installed) {
|
||||||
|
qWarning() << "[xray] failed to install xray routes on" << m_uplinkIfaceName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) {
|
if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) {
|
||||||
qDebug() << "[xray] sockopt failed: " << err;
|
qDebug() << "[xray] sockopt failed: " << err;
|
||||||
amnezia_xray_free(err);
|
amnezia_xray_free(err);
|
||||||
@@ -75,6 +91,14 @@ bool Xray::stopXray()
|
|||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
if (!m_uplinkIfaceName.isEmpty()) {
|
||||||
|
RouterMac::Instance().routeDeleteXray(m_uplinkIfaceName, m_uplinkGateway);
|
||||||
|
}
|
||||||
|
m_uplinkIfaceName.clear();
|
||||||
|
m_uplinkGateway.clear();
|
||||||
|
#endif
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user