Compare commits

..

30 Commits

Author SHA1 Message Date
Mykola Baibuz
4205bb1d31 fix: flush dns after in connected status 2025-08-20 13:44:55 +03:00
Mykola Baibuz
5570cb94b5 Setup DNS resolver for Linux IPSec 2025-08-19 19:44:58 +03:00
Mykola Baibuz
45323e67fb add trace info 2025-08-18 15:36:11 +03:00
Mykola Baibuz
ae3ed9c741 Merge branch 'dev' into feature/linux-ipsec 2025-08-10 02:47:32 -07:00
Mykola Baibuz
f47e4cb729 Enable PFS for Linux IPSec 2025-02-07 12:58:37 +02:00
Mykola Baibuz
b5cbcc5f2a Merge branch 'dev' of https://github.com/amnezia-vpn/amnezia-client into feature/linux-ipsec 2025-02-07 12:48:57 +02:00
Mykola Baibuz
f18d980bdb Merge branch 'dev' into feature/linux-ipsec 2025-02-07 12:48:41 +02:00
Mykola Baibuz
81cfab3566 fix: broken routing 2025-01-27 17:20:05 +02:00
Mykola Baibuz
7f123dabac Merge remote-tracking branch 'origin/bugfix/pre-release-hotfixes' into feature/linux-ipsec 2025-01-27 16:32:46 +02:00
Mykola Baibuz
de0b400934 Merge remote-tracking branch 'origin/bugfix/pre-release-hotfixes' into feature/linux-ipsec 2025-01-26 16:36:50 +02:00
Mykola Baibuz
dcde24649f Merge branch 'dev' into feature/linux-ipsec 2025-01-24 22:00:16 +02:00
vladimir.kuznetsov
b717560047 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2025-01-08 14:41:02 +07:00
vladimir.kuznetsov
afd2542a11 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/linux-ipsec 2024-12-19 14:28:20 +07:00
Pokamest Nikak
1438a21902 Merge branch 'dev' into feature/linux-ipsec 2024-09-11 19:41:20 +01:00
Mykola Baibuz
4147632a62 Fix Android build 2024-08-30 22:15:51 +03:00
Mykola Baibuz
948ab4cf71 Set local IPSec VPN address 2024-08-30 22:10:39 +03:00
Mykola Baibuz
f54308e4f4 Merge branch 'dev' into feature/linux-ipsec 2024-08-30 21:50:40 +03:00
Mykola Baibuz
052261c2b4 Get Linux IPSec tunnel status 2024-08-30 21:46:52 +03:00
Mykola Baibuz
3cec0dc2a7 Restart IPSec service before VPN connect 2024-08-29 23:18:21 +03:00
Mykola Baibuz
ad61ef0b22 Cleanup ipsec.secrets from duplicates 2024-08-29 00:24:51 +03:00
Mykola Baibuz
63c569c3d2 Setup routing for Linux IPSec 2024-08-25 00:26:32 +03:00
Mykola Baibuz
30df4c6800 Merge branch 'feature/linux-ipsec' of https://github.com/amnezia-vpn/amnezia-client into feature/linux-ipsec 2024-08-24 00:59:37 +03:00
Mykola Baibuz
a96f9dc18a Start and Stop for Linux tunnel 2024-08-24 00:57:47 +03:00
Mykola Baibuz
fb63cdf7e9 Fix work with PKCS12 TempFile 2024-08-20 22:45:06 +03:00
Mykola Baibuz
2d3b9c2752 Windows import PFX changes 2024-08-20 13:44:33 +03:00
Mykola Baibuz
09c58cb39e Fix certwrite for Win IPSec 2024-08-19 18:46:53 +03:00
Mykola Baibuz
654d219e7e Fix Win Build for IPSec protocol 2024-08-18 13:53:38 +03:00
Mykola Baibuz
89d4c18e87 Update IPSec configs templates 2024-08-18 01:46:06 +03:00
Mykola Baibuz
b0b185027e Linux IPSec initial 2024-08-01 21:37:56 +03:00
Mykola Baibuz
90912f9231 Fix Windows IPsec 2024-07-26 00:55:13 +03:00
31 changed files with 932 additions and 767 deletions

View File

@@ -174,6 +174,15 @@ endif()
if(LINUX AND NOT ANDROID)
set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_linux.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_linux.cpp
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))

View File

@@ -64,6 +64,26 @@ QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, Do
return "";
}
#if defined(Q_OS_LINUX)
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::ipsec_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
config.replace("$CLIENT_NAME", connData.clientId);
config.replace("$UUID1", QUuid::createUuid().toString());
config.replace("$SERVER_ADDR", connData.host);
QJsonObject jConfig;
jConfig[config_key::config] = config;
jConfig[config_key::hostName] = connData.host;
jConfig[config_key::userName] = connData.clientId;
jConfig[config_key::cert] = QString(connData.clientCert.toBase64());
jConfig[config_key::cacert] = QString(connData.caCert);
jConfig[config_key::password] = connData.password;
return QJsonDocument(jConfig).toJson();
#endif
return genIkev2Config(connData);
}
@@ -73,6 +93,7 @@ QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.clientCert.toBase64());
config[config_key::cacert] = QString(connData.caCert);
config[config_key::password] = connData.password;
return QJsonDocument(config).toJson();
@@ -115,3 +136,22 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData)
return config;
}
QString Ikev2Configurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json;
json[config_key::config] = protocolConfigString;
return QJsonDocument(json).toJson();
}
QString Ikev2Configurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json;
json[config_key::config] = protocolConfigString;
return QJsonDocument(json).toJson();
}

View File

@@ -27,6 +27,10 @@ public:
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
QString genIPSecConfig(const ConnectionData &connData);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode);

View File

@@ -257,7 +257,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{
#ifdef Q_OS_WINDOWS
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)
return true;
#elif defined(Q_OS_IOS)
@@ -306,13 +306,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::SSXray: return true;
default: return false;
}
#elif defined(Q_OS_LINUX)
switch (c) {
case DockerContainer::Ipsec: return false;
default: return true;
}
#else
return false;
#endif

View File

@@ -3,22 +3,11 @@
#include <algorithm>
#include <random>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMetaObject>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QRandomGenerator>
#include <QDataStream>
#include <QSslConfiguration>
#include <QSslSocket>
#include <QRemoteObjectPendingReply>
#include <QThread>
#include <QUrl>
#include <QtEndian>
#include <QDebug>
#include "QBlockCipher.h"
#include "QRsa.h"
@@ -77,15 +66,10 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
const QUrl originalUrl = request.url();
const QString originalHost = originalUrl.host();
const QString resolvedIp = addKillSwitchExceptionForUrl(originalUrl);
if (!resolvedIp.isEmpty() && resolvedIp != originalHost) {
QUrl ipUrl = originalUrl;
ipUrl.setHost(resolvedIp);
request.setUrl(ipUrl);
request.setPeerVerifyName(originalHost);
request.setRawHeader("Host", originalHost.toUtf8());
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
@@ -144,15 +128,10 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
const QUrl originalUrl = request.url();
const QString originalHost = originalUrl.host();
const QString resolvedIp = addKillSwitchExceptionForUrl(originalUrl);
if (!resolvedIp.isEmpty() && resolvedIp != originalHost) {
QUrl ipUrl = originalUrl;
ipUrl.setHost(resolvedIp);
request.setUrl(ipUrl);
request.setPeerVerifyName(originalHost);
request.setRawHeader("Host", originalHost.toUtf8());
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
@@ -383,344 +362,3 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
}
}
}
QString GatewayController::addKillSwitchExceptionForUrl(const QUrl &url)
{
#ifdef AMNEZIA_DESKTOP
const QString host = url.host();
if (host.isEmpty()) {
return {};
}
const QString resolvedIp = resolveHost(host);
if (resolvedIp.isEmpty()) {
qWarning() << "Failed to resolve host for KillSwitch exception" << host;
return {};
}
if (!addKillSwitchException(QStringList { resolvedIp })) {
qWarning() << "Failed to add KillSwitch exception" << resolvedIp;
return {};
}
return resolvedIp;
#else
Q_UNUSED(url);
return {};
#endif
}
QString GatewayController::resolveHost(const QString &host)
{
#ifdef AMNEZIA_DESKTOP
if (!m_isStrictKillSwitchEnabled) {
return NetworkUtilities::getIPAddress(host);
}
QString resolvedIp = NetworkUtilities::getIPAddress(host);
if (!resolvedIp.isEmpty()) {
return resolvedIp;
}
qDebug() << "resolveHost: falling back to resolveHostViaOpenDns" << host;
resolvedIp = resolveHostViaOpenDns(host);
if (!resolvedIp.isEmpty()) {
return resolvedIp;
}
qWarning() << "OpenDNS fallback failed" << host;
qDebug() << "resolveHost: falling back to resolveHostViaQuad9" << host;
resolvedIp = resolveHostViaQuad9(host);
if (resolvedIp.isEmpty()) {
qWarning() << "Quad9 fallback failed" << host;
}
return resolvedIp;
#else
return NetworkUtilities::getIPAddress(host);
#endif
}
#ifdef AMNEZIA_DESKTOP
bool GatewayController::addKillSwitchException(const QStringList &ranges)
{
auto ipcInterface = IpcClient::Interface();
if (!ipcInterface) {
qWarning() << "IPC interface is null, cannot add KillSwitch exception";
return false;
}
const auto waitForReply = [](QRemoteObjectPendingReply<bool> reply) -> bool {
if (!reply.waitForFinished()) {
qWarning() << "Timed out waiting for KillSwitch exception reply";
return false;
}
return reply.returnValue();
};
QRemoteObjectPendingReply<bool> reply;
if (ipcInterface->thread() == QThread::currentThread()) {
reply = ipcInterface->addKillSwitchAllowedRange(ranges);
} else {
const bool invoked = QMetaObject::invokeMethod(ipcInterface.data(),
[&reply, ipcInterface, ranges]() {
reply = ipcInterface->addKillSwitchAllowedRange(ranges);
},
Qt::BlockingQueuedConnection);
if (!invoked) {
qWarning() << "Failed to invoke KillSwitch exception update via queued connection";
return false;
}
}
const bool result = waitForReply(reply);
return result;
}
bool GatewayController::removeKillSwitchException(const QStringList &ranges)
{
auto ipcInterface = IpcClient::Interface();
if (!ipcInterface) {
qWarning() << "IPC interface is null, cannot remove KillSwitch exception";
return false;
}
const auto waitForReply = [](QRemoteObjectPendingReply<bool> reply) -> bool {
if (!reply.waitForFinished()) {
qWarning() << "Timed out waiting for KillSwitch removal reply";
return false;
}
return reply.returnValue();
};
QRemoteObjectPendingReply<bool> reply;
if (ipcInterface->thread() == QThread::currentThread()) {
reply = ipcInterface->removeKillSwitchAllowedRange(ranges);
} else {
const bool invoked = QMetaObject::invokeMethod(ipcInterface.data(),
[&reply, ipcInterface, ranges]() {
reply = ipcInterface->removeKillSwitchAllowedRange(ranges);
},
Qt::BlockingQueuedConnection);
if (!invoked) {
qWarning() << "Failed to invoke KillSwitch removal via queued connection";
return false;
}
}
const bool result = waitForReply(reply);
return result;
}
QString GatewayController::resolveHostViaOpenDns(const QString &host)
{
const QString fallbackIp = QStringLiteral("146.112.41.2");
const QString dohHostname = QStringLiteral("doh.opendns.com");
const QUrl dohEndpoint(QStringLiteral("https://%1/dns-query").arg(fallbackIp));
if (!addKillSwitchException(QStringList { fallbackIp })) {
qWarning() << "Failed to add fallback KillSwitch exception" << fallbackIp;
}
QNetworkRequest request(dohEndpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/dns-message"));
request.setRawHeader("Accept", "application/dns-message");
request.setRawHeader("Host", dohHostname.toUtf8());
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setPeerVerifyName(dohHostname);
QByteArray payload = buildDnsQuery(host);
QNetworkReply *reply = amnApp->networkManager()->post(request, payload);
if (!reply) {
qWarning() << "Failed to create DoH request" << host;
return {};
}
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QByteArray dnsResponse;
if (reply->error() == QNetworkReply::NoError) {
dnsResponse = reply->readAll();
} else {
qWarning() << "DoH request failed" << host << reply->errorString();
}
reply->deleteLater();
if (dnsResponse.isEmpty()) {
return {};
}
const QString resolvedIp = parseDnsResponse(dnsResponse);
return resolvedIp;
}
QString GatewayController::resolveHostViaQuad9(const QString &host)
{
const QString dohHostname = QStringLiteral("dns.quad9.net");
const QString fallbackIp = QStringLiteral("149.112.112.112");
QByteArray payload = buildDnsQuery(host);
const QUrl dohEndpoint(QStringLiteral("https://%1/dns-query").arg(fallbackIp));
if (!addKillSwitchException(QStringList { fallbackIp })) {
qWarning() << "resolveHostViaQuad9: failed to add KillSwitch exception" << fallbackIp;
}
QNetworkRequest request(dohEndpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/dns-message"));
request.setRawHeader("Accept", "application/dns-message");
request.setRawHeader("Host", dohHostname.toUtf8());
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setPeerVerifyName(dohHostname);
QNetworkReply *reply = amnApp->networkManager()->post(request, payload);
if (!reply) {
qWarning() << "resolveHostViaQuad9: failed to create DoH request" << host << fallbackIp;
return {};
}
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QByteArray dnsResponse;
if (reply->error() == QNetworkReply::NoError) {
dnsResponse = reply->readAll();
} else {
qWarning() << "resolveHostViaQuad9: DoH request failed" << host << fallbackIp << reply->errorString();
}
reply->deleteLater();
if (dnsResponse.isEmpty()) {
return {};
}
const QString resolvedIp = parseDnsResponse(dnsResponse);
return resolvedIp;
}
QByteArray GatewayController::buildDnsQuery(const QString &host) const
{
QByteArray query;
QDataStream stream(&query, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
quint16 transactionId = QRandomGenerator::system()->generate();
stream << transactionId;
stream << static_cast<quint16>(0x0100); // standard query with recursion desired
stream << static_cast<quint16>(1); // QDCOUNT
stream << static_cast<quint16>(0); // ANCOUNT
stream << static_cast<quint16>(0); // NSCOUNT
stream << static_cast<quint16>(0); // ARCOUNT
const QByteArray hostUtf8 = host.toUtf8();
const QList<QByteArray> labels = hostUtf8.split('.');
for (const QByteArray &label : labels) {
stream << static_cast<quint8>(label.size());
stream.writeRawData(label.constData(), label.size());
}
stream << static_cast<quint8>(0); // end of QNAME
stream << static_cast<quint16>(1); // QTYPE A
stream << static_cast<quint16>(1); // QCLASS IN
return query;
}
QString GatewayController::parseDnsResponse(const QByteArray &response) const
{
if (response.size() < 12) {
qWarning() << "DNS response too short" << response.size();
return {};
}
QDataStream stream(response);
stream.setByteOrder(QDataStream::BigEndian);
quint16 transactionId;
quint16 flags;
quint16 qdCount;
quint16 anCount;
quint16 nsCount;
quint16 arCount;
stream >> transactionId >> flags >> qdCount >> anCount >> nsCount >> arCount;
if ((flags & 0x000F) != 0) {
qWarning() << "DNS response contains error" << flags;
return {};
}
int offset = 12;
for (int i = 0; i < qdCount; ++i) {
offset = skipDnsName(response, offset);
if (offset < 0 || offset + 4 > response.size()) {
qWarning() << "Invalid DNS question section";
return {};
}
offset += 4;
}
const uchar *data = reinterpret_cast<const uchar *>(response.constData());
for (int i = 0; i < anCount; ++i) {
int nameOffset = skipDnsName(response, offset);
if (nameOffset < 0 || nameOffset + 10 > response.size()) {
qWarning() << "Invalid DNS answer section";
return {};
}
offset = nameOffset;
quint16 type = qFromBigEndian<quint16>(data + offset);
quint16 dnsClass = qFromBigEndian<quint16>(data + offset + 2);
quint32 ttl = qFromBigEndian<quint32>(data + offset + 4);
Q_UNUSED(ttl);
quint16 rdLength = qFromBigEndian<quint16>(data + offset + 8);
offset += 10;
if (offset + rdLength > response.size()) {
qWarning() << "Invalid RDATA length" << rdLength;
return {};
}
if (type == 1 && dnsClass == 1 && rdLength == 4) {
const quint8 b1 = data[offset];
const quint8 b2 = data[offset + 1];
const quint8 b3 = data[offset + 2];
const quint8 b4 = data[offset + 3];
return QStringLiteral("%1.%2.%3.%4").arg(b1).arg(b2).arg(b3).arg(b4);
}
offset += rdLength;
}
return {};
}
int GatewayController::skipDnsName(const QByteArray &message, int offset) const
{
while (offset < message.size()) {
quint8 length = static_cast<quint8>(message.at(offset));
if (length == 0) {
return offset + 1;
}
if ((length & 0xC0) == 0xC0) {
if (offset + 2 > message.size()) {
return -1;
}
return offset + 2;
}
++offset;
offset += length;
if (offset > message.size()) {
return -1;
}
}
return -1;
}
#endif

View File

@@ -27,17 +27,6 @@ private:
const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
QString addKillSwitchExceptionForUrl(const QUrl &url);
QString resolveHost(const QString &host);
#ifdef AMNEZIA_DESKTOP
bool addKillSwitchException(const QStringList &ranges);
bool removeKillSwitchException(const QStringList &ranges);
QString resolveHostViaOpenDns(const QString &host);
QString resolveHostViaQuad9(const QString &host);
QByteArray buildDnsQuery(const QString &host) const;
QString parseDnsResponse(const QByteArray &response) const;
int skipDnsName(const QByteArray &message, int offset) const;
#endif
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;

View File

@@ -50,6 +50,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
case ProtocolScriptType::ipsec_template: return QLatin1String("template.conf");
default: return QString();
}
}

View File

@@ -28,7 +28,8 @@ enum ProtocolScriptType {
openvpn_template,
wireguard_template,
awg_template,
xray_template
xray_template,
ipsec_template
};

View File

@@ -0,0 +1,185 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QNetworkInterface>
#include <QThread>
#include <chrono>
#include "core/networkUtilities.h"
#include "settings.h"
#include "logger.h"
#include "ikev2_vpn_protocol_linux.h"
#include "utilities.h"
#include "core/ipcclient.h"
#include <openssl/pkcs12.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
static Ikev2Protocol* self = nullptr;
Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
qDebug() << "IpsecProtocol::Ikev2Protocol()";
self = this;
readIkev2Configuration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface();
m_vpnGateway = "192.168.43.10";
m_vpnLocalAddress = "192.168.43.10";
m_remoteAddress = NetworkUtilities::getIPAddress(configuration.value(amnezia::config_key::hostName).toString());
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_configData = configuration;
}
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
Ikev2Protocol::stop();
}
void Ikev2Protocol::stop()
{
setConnectionState(Vpn::ConnectionState::Disconnected);
Ikev2Protocol::disconnect_vpn();
qDebug() << "IpsecProtocol::stop()";
}
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
qDebug() << "IpsecProtocol::readIkev2Configuration()";
QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object();
}
ErrorCode Ikev2Protocol::start()
{
qDebug() << "IpsecProtocol::start()";
STACK_OF(X509) *certstack = sk_X509_new_null();
BIO *p12 = BIO_new(BIO_s_mem());
EVP_PKEY *pkey;
X509 *cert;
BIO_write(p12, QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()),
QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()).size());
PKCS12 *pkcs12 = d2i_PKCS12_bio(p12, NULL);
PKCS12_parse(pkcs12, m_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack);
BIO *bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, cert);
BUF_MEM *mem = NULL;
BIO_get_mem_ptr(bio, &mem);
std::string pem(mem->data, mem->length);
QString alias(pem.c_str());
IpcClient::Interface()->writeIPsecUserCert(alias, m_config[config_key::userName].toString());
IpcClient::Interface()->writeIPsecConfig(m_config[config_key::config].toString());
IpcClient::Interface()->writeIPsecCaCert(m_config[config_key::cacert].toString(), m_config[config_key::userName].toString());
IpcClient::Interface()->writeIPsecPrivate(m_config[config_key::cert].toString(), m_config[config_key::userName].toString());
IpcClient::Interface()->writeIPsecPrivatePass(m_config[config_key::password].toString(), m_config[config_key::hostName].toString(),
m_config[config_key::userName].toString());
connect_to_vpn("ikev2-vpn");
if (!IpcClient::Interface()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
QString connectionStatus;
auto futureResult = IpcClient::Interface()->getTunnelStatus("ikev2-vpn");
futureResult.waitForFinished();
if (futureResult.returnValue().isEmpty()) {
auto futureResult = IpcClient::Interface()->getTunnelStatus("ikev2-vpn");
futureResult.waitForFinished();
}
connectionStatus = futureResult.returnValue();
if (connectionStatus.contains("ESTABLISHED")) {
QStringList lines = connectionStatus.split('\n');
for (auto iter = lines.begin(); iter!=lines.end(); iter++)
{
if (iter->contains("0.0.0.0/0")) {
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_configData.value(amnezia::config_key::dns1).toString().
contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
}
m_vpnGateway = iter->split("===", Qt::SkipEmptyParts).first();
m_vpnGateway = m_vpnGateway.split(" ").at(2);
m_vpnGateway = m_vpnGateway.split("/").first();
m_vpnLocalAddress = m_vpnGateway;
// killSwitch toggle
if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) {
m_config.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enableKillSwitch(m_config, 0);
}
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
}
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 (netInterfaces.at(i).addressEntries().at(j).ip().toString() == m_vpnGateway)
{
IpcClient::Interface()->updateResolvers(netInterfaces.at(i).humanReadableName(), dnsAddr);
}
}
}
IpcClient::Interface()->StopRoutingIpv6();
}
}
setConnectionState(Vpn::ConnectionState::Connected);
} else {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
return ErrorCode::NoError;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::create_new_vpn(const QString & vpn_name,
const QString & serv_addr) {
qDebug() << "Ikev2Protocol::create_new_vpn()";
return true;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name) {
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::connect_to_vpn(const QString &vpn_name) {
qDebug() << "IpsecProtocol::connect_to_vpn()";
IpcClient::Interface()->startIPsec(vpn_name);
QThread::msleep(3000);
return true;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::disconnect_vpn() {
qDebug() << "IpsecProtocol::disconnect_vpn()";
IpcClient::Interface()->stopIPsec("ikev2-vpn");
IpcClient::Interface()->disableKillSwitch();
IpcClient::Interface()->StartRoutingIpv6();
return true;
}

View File

@@ -0,0 +1,52 @@
#ifndef IKEV2_VPN_PROTOCOL_LINUX_H
#define IKEV2_VPN_PROTOCOL_LINUX_H
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QTimer>
#include "vpnprotocol.h"
#include <string>
#include <memory>
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>
class Ikev2Protocol : public VpnProtocol
{
Q_OBJECT
public:
explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr);
virtual ~Ikev2Protocol() override;
ErrorCode start() override;
void stop() override;
static QString tunnelName() { return "AmneziaVPN IKEv2"; }
private:
void readIkev2Configuration(const QJsonObject &configuration);
private:
QJsonObject m_config;
QJsonObject m_configData;
QString m_remoteAddress;
int m_routeMode;
bool create_new_vpn(const QString & vpn_name,
const QString & serv_addr);
bool delete_vpn_connection(const QString &vpn_name);
bool connect_to_vpn(const QString & vpn_name);
bool disconnect_vpn();
};
#endif // IKEV2_VPN_PROTOCOL_LINUX_H

View File

@@ -172,7 +172,8 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object();
}
ErrorCode Ikev2Protocol::start()

View File

@@ -24,6 +24,7 @@ namespace amnezia
constexpr char description[] = "description";
constexpr char name[] = "name";
constexpr char cert[] = "cert";
constexpr char cacert[] = "cacert";
constexpr char config[] = "config";
constexpr char containers[] = "containers";

View File

@@ -16,6 +16,10 @@
#include "ikev2_vpn_protocol_windows.h"
#endif
#ifdef Q_OS_LINUX
#include "ikev2_vpn_protocol_linux.h"
#endif
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
: QObject(parent),
m_connectionState(Vpn::ConnectionState::Unknown),
@@ -106,7 +110,7 @@ QString VpnProtocol::vpnGateway() const
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{
switch (container) {
#if defined(Q_OS_WINDOWS)
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))

View File

@@ -77,6 +77,7 @@
<file>server_scripts/ipsec/mobileconfig.plist</file>
<file>server_scripts/ipsec/run_container.sh</file>
<file>server_scripts/ipsec/start.sh</file>
<file>server_scripts/ipsec/template.conf</file>
<file>server_scripts/ipsec/strongswan.profile</file>
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
<file>server_scripts/openvpn_cloak/Dockerfile</file>

View File

@@ -242,6 +242,7 @@ conn ikev2-cp
dpdtimeout=120
dpdaction=clear
auto=add
authby=rsa-sha1
ikev2=insist
rekey=no
pfs=yes

View File

@@ -0,0 +1,28 @@
config setup
charondebug="ike 1, knl 1, cfg 0"
uniqueids=no
conn ikev2-vpn
auto=add
type=tunnel
keyexchange=ikev2
fragmentation=yes
forceencaps=yes
dpdaction=clear
dpddelay=300s
rekey=no
leftid=$CLIENT_NAME
leftcert=$CLIENT_NAME.crt
leftdns=$PRIMARY_DNS,$SECONDARY_DNS
leftsendcert=always
leftsourceip=%config
right=$SERVER_IP_ADDRESS
rightsubnet=0.0.0.0/0
rightsendcert=never
eap_identity=%identity
encapsulation=yes
pfs=yes
ike=aes256-sha256-modp2048,aes256-sha1-modp1024,3des-sha1-modp1024
esp=aes256-sha256,aes256-sha1,3des-sha1

View File

@@ -34,7 +34,7 @@ PageType {
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.top: backButtonLayout.bottom
anchors.bottom: saveButton.top
anchors.right: parent.right
anchors.left: parent.left

View File

@@ -37,7 +37,7 @@ PageType {
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right

View File

@@ -17,413 +17,427 @@ import "../Components"
PageType {
id: root
BackButtonType {
id: backButton
ColumnLayout {
id: backButtonLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
BackButtonType {
id: backButton
}
}
ListViewType {
id: listView
anchors.top: backButton.bottom
FlickableType {
id: fl
anchors.top: backButtonLayout.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
contentHeight: content.implicitHeight
enabled: ServersModel.isProcessedServerHasWriteAccess()
Column {
id: content
header: ColumnLayout {
width: listView.width
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
BaseHeaderType {
id: header
enabled: ServersModel.isProcessedServerHasWriteAccess()
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
ListView {
id: listview
headerText: qsTr("OpenVPN Settings")
}
}
width: parent.width
height: listview.contentItem.height
model: OpenVpnConfigModel
clip: true
interactive: false
delegate: ColumnLayout {
width: listView.width
model: OpenVpnConfigModel
spacing: 0
delegate: Item {
id: delegateItem
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
implicitWidth: listview.width
implicitHeight: col.implicitHeight
enabled: listView.enabled
ColumnLayout {
id: col
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
}
}
anchors.leftMargin: 16
anchors.rightMargin: 16
checkEmptyText: true
}
spacing: 0
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
BaseHeaderType {
Layout.fillWidth: true
headerText: qsTr("OpenVPN settings")
}
text: qsTr("Network protocol")
}
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
TransportProtoSelector {
id: transportProtoSelector
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.fillWidth: true
Layout.topMargin: 32
rootWidth: root.width
enabled: delegateItem.isEnabled
enabled: isTransportProtoEditable
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
currentIndex: {
return transportProto === "tcp" ? 1 : 0
}
parentFlickable: fl
onCurrentIndexChanged: {
if (transportProto === "tcp" && currentIndex === 0) {
transportProto = "udp"
} else if (transportProto === "udp" && currentIndex === 1) {
transportProto = "tcp"
}
}
}
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
}
}
TextFieldWithHeaderType {
id: portTextField
checkEmptyText: true
}
Layout.fillWidth: true
Layout.topMargin: 40
Layout.leftMargin: 16
Layout.rightMargin: 16
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 32
enabled: listView.enabled
text: qsTr("Network protocol")
}
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
TransportProtoSelector {
id: transportProtoSelector
Layout.fillWidth: true
Layout.topMargin: 16
rootWidth: root.width
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
}
}
enabled: isTransportProtoEditable
checkEmptyText: true
}
currentIndex: {
return transportProto === "tcp" ? 1 : 0
}
SwitcherType {
id: autoNegotiateEncryprionSwitcher
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Auto-negotiate encryption")
checked: autoNegotiateEncryprion
onCheckedChanged: {
if (checked !== autoNegotiateEncryprion) {
autoNegotiateEncryprion = checked
}
}
}
DropDownType {
id: hashDropDown
Layout.fillWidth: true
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: !autoNegotiateEncryprionSwitcher.checked
descriptionText: qsTr("Hash")
headerText: qsTr("Hash")
drawerParent: root
listView: ListViewWithRadioButtonType {
id: hashListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("SHA512") }
ListElement { name : qsTr("SHA384") }
ListElement { name : qsTr("SHA256") }
ListElement { name : qsTr("SHA3-512") }
ListElement { name : qsTr("SHA3-384") }
ListElement { name : qsTr("SHA3-256") }
ListElement { name : qsTr("whirlpool") }
ListElement { name : qsTr("BLAKE2b512") }
ListElement { name : qsTr("BLAKE2s256") }
ListElement { name : qsTr("SHA1") }
}
clickedFunction: function() {
hashDropDown.text = selectedText
hash = hashDropDown.text
hashDropDown.closeTriggered()
}
Component.onCompleted: {
hashDropDown.text = hash
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hashDropDown.text) {
currentIndex = i
onCurrentIndexChanged: {
if (transportProto === "tcp" && currentIndex === 0) {
transportProto = "udp"
} else if (transportProto === "udp" && currentIndex === 1) {
transportProto = "tcp"
}
}
}
}
}
}
DropDownType {
id: cipherDropDown
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
TextFieldWithHeaderType {
id: portTextField
enabled: !autoNegotiateEncryprionSwitcher.checked
Layout.fillWidth: true
Layout.topMargin: 40
parentFlickable: fl
descriptionText: qsTr("Cipher")
headerText: qsTr("Cipher")
enabled: delegateItem.isEnabled
drawerParent: root
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
listView: ListViewWithRadioButtonType {
id: cipherListView
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
}
}
rootWidth: root.width
checkEmptyText: true
}
model: ListModel {
ListElement { name : qsTr("AES-256-GCM") }
ListElement { name : qsTr("AES-192-GCM") }
ListElement { name : qsTr("AES-128-GCM") }
ListElement { name : qsTr("AES-256-CBC") }
ListElement { name : qsTr("AES-192-CBC") }
ListElement { name : qsTr("AES-128-CBC") }
ListElement { name : qsTr("ChaCha20-Poly1305") }
ListElement { name : qsTr("ARIA-256-CBC") }
ListElement { name : qsTr("CAMELLIA-256-CBC") }
ListElement { name : qsTr("none") }
}
SwitcherType {
id: autoNegotiateEncryprionSwitcher
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
cipherDropDown.closeTriggered()
}
Layout.fillWidth: true
Layout.topMargin: 24
parentFlickable: fl
Component.onCompleted: {
cipherDropDown.text = cipher
text: qsTr("Auto-negotiate encryption")
checked: autoNegotiateEncryprion
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
onCheckedChanged: {
if (checked !== autoNegotiateEncryprion) {
autoNegotiateEncryprion = checked
}
}
}
}
}
}
Rectangle {
id: contentRect
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
DropDownType {
id: hashDropDown
Layout.fillWidth: true
Layout.topMargin: 20
Layout.preferredHeight: checkboxLayout.implicitHeight
color: AmneziaStyle.color.onyxBlack
radius: 16
enabled: !autoNegotiateEncryprionSwitcher.checked
ColumnLayout {
id: checkboxLayout
descriptionText: qsTr("Hash")
headerText: qsTr("Hash")
anchors.fill: parent
drawerParent: root
CheckBoxType {
id: tlsAuthCheckBox
Layout.fillWidth: true
listView: ListViewWithRadioButtonType {
id: hashListView
text: qsTr("TLS auth")
checked: tlsAuth
rootWidth: root.width
onCheckedChanged: {
if (checked !== tlsAuth) {
console.log("tlsAuth changed to: " + checked)
tlsAuth = checked
model: ListModel {
ListElement { name : qsTr("SHA512") }
ListElement { name : qsTr("SHA384") }
ListElement { name : qsTr("SHA256") }
ListElement { name : qsTr("SHA3-512") }
ListElement { name : qsTr("SHA3-384") }
ListElement { name : qsTr("SHA3-256") }
ListElement { name : qsTr("whirlpool") }
ListElement { name : qsTr("BLAKE2b512") }
ListElement { name : qsTr("BLAKE2s256") }
ListElement { name : qsTr("SHA1") }
}
clickedFunction: function() {
hashDropDown.text = selectedText
hash = hashDropDown.text
hashDropDown.closeTriggered()
}
Component.onCompleted: {
hashDropDown.text = hash
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hashDropDown.text) {
currentIndex = i
}
}
}
}
}
}
DividerType {}
DropDownType {
id: cipherDropDown
Layout.fillWidth: true
Layout.topMargin: 16
CheckBoxType {
id: blockDnsCheckBox
Layout.fillWidth: true
enabled: !autoNegotiateEncryprionSwitcher.checked
text: qsTr("Block DNS requests outside of VPN")
checked: blockDns
descriptionText: qsTr("Cipher")
headerText: qsTr("Cipher")
onCheckedChanged: {
if (checked !== blockDns) {
blockDns = checked
drawerParent: root
listView: ListViewWithRadioButtonType {
id: cipherListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("AES-256-GCM") }
ListElement { name : qsTr("AES-192-GCM") }
ListElement { name : qsTr("AES-128-GCM") }
ListElement { name : qsTr("AES-256-CBC") }
ListElement { name : qsTr("AES-192-CBC") }
ListElement { name : qsTr("AES-128-CBC") }
ListElement { name : qsTr("ChaCha20-Poly1305") }
ListElement { name : qsTr("ARIA-256-CBC") }
ListElement { name : qsTr("CAMELLIA-256-CBC") }
ListElement { name : qsTr("none") }
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
cipherDropDown.closeTriggered()
}
Component.onCompleted: {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
}
}
}
}
}
}
}
}
SwitcherType {
id: additionalClientCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
Rectangle {
id: contentRect
Layout.fillWidth: true
Layout.topMargin: 32
Layout.preferredHeight: checkboxLayout.implicitHeight
color: AmneziaStyle.color.onyxBlack
radius: 16
checked: additionalClientCommands !== ""
Connections {
target: tlsAuthCheckBox
enabled: !GC.isMobile()
text: qsTr("Additional client configuration commands")
function onFocusChanged() {
if (tlsAuthCheckBox.activeFocus) {
fl.ensureVisible(contentRect)
}
}
}
onCheckedChanged: {
if (!checked) {
additionalClientCommands = ""
}
}
}
ColumnLayout {
id: checkboxLayout
TextAreaType {
id: additionalClientCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
anchors.fill: parent
CheckBoxType {
id: tlsAuthCheckBox
Layout.fillWidth: true
visible: additionalClientCommandsSwitcher.checked
text: qsTr("TLS auth")
checked: tlsAuth
textAreaText: additionalClientCommands
placeholderText: qsTr("Commands:")
onCheckedChanged: {
if (checked !== tlsAuth) {
console.log("tlsAuth changed to: " + checked)
tlsAuth = checked
}
}
}
textArea.onEditingFinished: {
if (additionalClientCommands !== textAreaText) {
additionalClientCommands = textAreaText
}
}
}
DividerType {}
SwitcherType {
id: additionalServerCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
CheckBoxType {
id: blockDnsCheckBox
Layout.fillWidth: true
checked: additionalServerCommands !== ""
text: qsTr("Block DNS requests outside of VPN")
checked: blockDns
text: qsTr("Additional server configuration commands")
onCheckedChanged: {
if (!checked) {
additionalServerCommands = ""
}
}
}
TextAreaType {
id: additionalServerCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: additionalServerCommandsSwitcher.checked
textAreaText: additionalServerCommands
placeholderText: qsTr("Commands:")
textArea.onEditingFinished: {
if (additionalServerCommands !== textAreaText) {
additionalServerCommands = textAreaText
}
}
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: vpnAddressSubnetTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
onClicked: function() {
forceActiveFocus()
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
onCheckedChanged: {
if (checked !== blockDns) {
blockDns = checked
}
}
}
}
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveButton.forceActiveFocus()
SwitcherType {
id: additionalClientCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 32
parentFlickable: fl
checked: additionalClientCommands !== ""
text: qsTr("Additional client configuration commands")
onCheckedChanged: {
if (!checked) {
additionalClientCommands = ""
}
}
}
TextAreaType {
id: additionalClientCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
visible: additionalClientCommandsSwitcher.checked
parentFlickable: fl
textAreaText: additionalClientCommands
placeholderText: qsTr("Commands:")
textArea.onEditingFinished: {
if (additionalClientCommands !== textAreaText) {
additionalClientCommands = textAreaText
}
}
}
SwitcherType {
id: additionalServerCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 16
parentFlickable: fl
checked: additionalServerCommands !== ""
text: qsTr("Additional server configuration commands")
onCheckedChanged: {
if (!checked) {
additionalServerCommands = ""
}
}
}
TextAreaType {
id: additionalServerCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
visible: additionalServerCommandsSwitcher.checked
textAreaText: additionalServerCommands
placeholderText: qsTr("Commands:")
parentFlickable: fl
textArea.onEditingFinished: {
if (additionalServerCommands !== textAreaText) {
additionalServerCommands = textAreaText
}
}
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: vpnAddressSubnetTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
parentFlickable: fl
onClicked: function() {
forceActiveFocus()
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
Keys.onEnterPressed: saveButton.clicked()
Keys.onReturnPressed: saveButton.clicked()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
Keys.onEnterPressed: saveButton.clicked()
Keys.onReturnPressed: saveButton.clicked()
}
}
}

View File

@@ -66,6 +66,8 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
@@ -85,6 +87,8 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5

View File

@@ -43,6 +43,8 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isVisible
@@ -66,6 +68,8 @@ PageType {
visible: GC.isDesktop()
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Close application")
leftImageSource: "qrc:/images/controls/x-circle.svg"

View File

@@ -66,13 +66,6 @@ PageType {
text: qsTr("If AmneziaDNS is not used or installed")
}
}
model: 1 // fake model to force the ListView to be created without a model
delegate: ColumnLayout {
width: listView.width
spacing: 16
TextFieldWithHeaderType {
id: primaryDns
@@ -103,6 +96,13 @@ PageType {
regularExpression: InstallController.ipAddressRegExp()
}
}
}
model: 1 // fake model to force the ListView to be created without a model
spacing: 16
delegate: ColumnLayout {
width: listView.width
BasicButtonType {
id: restoreDefaultButton
@@ -139,6 +139,10 @@ PageType {
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
}
footer: ColumnLayout {
width: listView.width
BasicButtonType {
id: saveButton

View File

@@ -64,15 +64,12 @@ PageType {
enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
checked: !SettingsController.strictKillSwitchEnabled
checkable: false
text: qsTr("Soft KillSwitch")
descriptionText: qsTr("Internet access is blocked if the VPN disconnects unexpectedly")
onClicked: function() {
if (SettingsController.strictKillSwitchEnabled) {
SettingsController.strictKillSwitchEnabled = false
}
SettingsController.strictKillSwitchEnabled = false
}
Keys.onEnterPressed: this.clicked()
@@ -87,18 +84,15 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
visible: false
enabled: false
// enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
checked: SettingsController.strictKillSwitchEnabled
checkable: false
text: qsTr("Strict KillSwitch")
descriptionText: qsTr("Internet connection is blocked even when VPN is turned off manually or hasn't started")
onClicked: function() {
if (SettingsController.strictKillSwitchEnabled) {
return
}
var headerText = qsTr("Just a little heads-up")
var descriptionText = qsTr("If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch.")
var yesButtonText = qsTr("Continue")

View File

@@ -18,8 +18,6 @@ PageType {
signal lastItemTabClickedSignal()
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
Connections {
target: InstallController
@@ -61,13 +59,15 @@ PageType {
target: ServersModel
function onProcessedServerIndexChanged() {
root.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
}
}
ListViewType {
id: listView
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
anchors.fill: parent
model: serverActions
@@ -107,7 +107,7 @@ PageType {
QtObject {
id: check
property bool isVisible: root.isServerWithWriteAccess
property bool isVisible: true
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
readonly property string description: qsTr("Add them to the application if they were not displayed")
readonly property var tColor: AmneziaStyle.color.paleGray
@@ -121,7 +121,7 @@ PageType {
QtObject {
id: reboot
property bool isVisible: root.isServerWithWriteAccess
property bool isVisible: true
readonly property string title: qsTr("Reboot server")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -181,7 +181,7 @@ PageType {
QtObject {
id: clear
property bool isVisible: root.isServerWithWriteAccess
property bool isVisible: true
readonly property string title: qsTr("Clear server from Amnezia software")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -240,7 +240,7 @@ PageType {
QtObject {
id: switch_to_premium
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed

View File

@@ -161,4 +161,10 @@ PageType {
}
}
}
ShareConnectionDrawer {
id: shareConnectionDrawer
anchors.fill: parent
}
}

View File

@@ -83,7 +83,9 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
if (IpcClient::Interface()) {
if (state == Vpn::ConnectionState::Connected) {
IpcClient::Interface()->resetIpStack();
IpcClient::Interface()->flushDns();
if (container != DockerContainer::Ipsec) {
IpcClient::Interface()->flushDns();
}
if (container != DockerContainer::Awg && container != DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();

View File

@@ -32,10 +32,21 @@ class IpcInterface
SLOT( bool disableAllTraffic() );
SLOT( bool refreshKillSwitch( bool enabled ) );
SLOT( bool addKillSwitchAllowedRange( const QStringList ranges ) );
SLOT( bool removeKillSwitchAllowedRange( const QStringList ranges ) );
SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) );
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
SLOT( bool writeIPsecCaCert(QString cacert, QString uuid) );
SLOT( bool writeIPsecPrivate(QString privKey, QString uuid) );
SLOT( bool writeIPsecConfig(QString config) );
SLOT( bool writeIPsecUserCert(QString usercert, QString uuid) );
SLOT( bool writeIPsecPrivatePass(QString pass, QString host, QString uuid) );
SLOT( bool stopIPsec(QString tunnelName) );
SLOT( bool startIPsec(QString tunnelName) );
SLOT( QString getTunnelStatus(QString tunnelName) );
};

View File

@@ -4,6 +4,7 @@
#include <QFileInfo>
#include <QLocalSocket>
#include <QObject>
#include <QJsonArray>
#include "logger.h"
#include "router.h"
@@ -161,6 +162,7 @@ void IpcServer::StartRoutingIpv6()
{
Router::StartRoutingIpv6();
}
void IpcServer::StopRoutingIpv6()
{
Router::StopRoutingIpv6();
@@ -189,11 +191,6 @@ bool IpcServer::addKillSwitchAllowedRange(QStringList ranges)
return KillSwitch::instance()->addAllowedRange(ranges);
}
bool IpcServer::removeKillSwitchAllowedRange(QStringList ranges)
{
return KillSwitch::instance()->removeAllowedRange(ranges);
}
bool IpcServer::disableAllTraffic()
{
return KillSwitch::instance()->disableAllTraffic();
@@ -209,6 +206,196 @@ bool IpcServer::disableKillSwitch()
return KillSwitch::instance()->disableKillSwitch();
}
bool IpcServer::startIPsec(QString tunnelName)
{
#ifdef Q_OS_LINUX
QProcess processSystemd;
QStringList commandsSystemd;
commandsSystemd << "systemctl" << "restart" << "ipsec";
processSystemd.start("sudo", commandsSystemd);
if (!processSystemd.waitForStarted(1000))
{
qDebug().noquote() << "Could not start ipsec tunnel!\n";
return false;
}
else if (!processSystemd.waitForFinished(2000))
{
qDebug().noquote() << "Could not start ipsec tunnel\n";
return false;
}
commandsSystemd.clear();
QThread::msleep(5000);
QProcess process;
QStringList commands;
commands << "ipsec" << "up" << QString("%1").arg(tunnelName);
process.start("sudo", commands);
if (!process.waitForStarted(1000))
{
qDebug().noquote() << "Could not start ipsec tunnel!\n";
return false;
}
else if (!process.waitForFinished(2000))
{
qDebug().noquote() << "Could not start ipsec tunnel\n";
return false;
}
commands.clear();
#endif
return true;
}
bool IpcServer::stopIPsec(QString tunnelName)
{
#ifdef Q_OS_LINUX
QProcess process;
QStringList commands;
commands << "ipsec" << "down" << QString("%1").arg(tunnelName);
process.start("sudo", commands);
if (!process.waitForStarted(1000))
{
qDebug().noquote() << "Could not stop ipsec tunnel\n";
return false;
}
else if (!process.waitForFinished(2000))
{
qDebug().noquote() << "Could not stop ipsec tunnel\n";
return false;
}
commands.clear();
#endif
return true;
}
bool IpcServer::writeIPsecConfig(QString config)
{
#ifdef Q_OS_LINUX
qDebug() << "IPSEC: IPSec config file";
QString configFile = QString("/etc/ipsec.conf");
QFile ipSecConfFile(configFile);
if (ipSecConfFile.open(QIODevice::WriteOnly)) {
ipSecConfFile.write(config.toUtf8());
ipSecConfFile.close();
}
#endif
return true;
}
bool IpcServer::writeIPsecUserCert(QString usercert, QString uuid)
{
#ifdef Q_OS_LINUX
qDebug() << "IPSEC: Write user cert " << uuid;
QString certName = QString("/etc/ipsec.d/certs/%1.crt").arg(uuid);
QFile userCertFile(certName);
if (userCertFile.open(QIODevice::WriteOnly)) {
userCertFile.write(usercert.toUtf8());
userCertFile.close();
}
#endif
return true;
}
bool IpcServer::writeIPsecCaCert(QString cacert, QString uuid)
{
#ifdef Q_OS_LINUX
qDebug() << "IPSEC: Write CA cert user " << uuid;
QString certName = QString("/etc/ipsec.d/cacerts/%1.crt").arg(uuid);
QFile caCertFile(certName);
if (caCertFile.open(QIODevice::WriteOnly)) {
caCertFile.write(cacert.toUtf8());
caCertFile.close();
}
#endif
return true;
}
bool IpcServer::writeIPsecPrivate(QString privKey, QString uuid)
{
#ifdef Q_OS_LINUX
qDebug() << "IPSEC: User private key " << uuid;
QString privateKey = QString("/etc/ipsec.d/private/%1.p12").arg(uuid);
QFile pKeyFile(privateKey);
if (pKeyFile.open(QIODevice::WriteOnly)) {
pKeyFile.write(QByteArray::fromBase64(privKey.toUtf8()));
pKeyFile.close();
}
#endif
return true;
}
bool IpcServer::writeIPsecPrivatePass(QString pass, QString host, QString uuid)
{
#ifdef Q_OS_LINUX
qDebug() << "IPSEC: User private key " << uuid;
const QString secretsFilename = "/etc/ipsec.secrets";
QStringList lines;
{
QFile secretsFile(secretsFilename);
if (secretsFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream edit(&secretsFile);
while (!edit.atEnd()) lines.push_back(edit.readLine());
}
secretsFile.close();
}
for (auto iter = lines.begin(); iter!=lines.end();)
{
if (iter->contains(host))
{
iter = lines.erase(iter);
}
else
{
++iter;
}
}
{
QFile secretsFile(secretsFilename);
if (secretsFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream edit(&secretsFile);
for (int i=0; i<lines.size(); i++) edit << lines[i] << Qt::endl;
}
QString P12 = QString("%any %1 : P12 %2.p12 \"%3\" \n").arg(host, uuid, pass);
secretsFile.write(P12.toUtf8());
secretsFile.close();
}
#endif
return true;
}
QString IpcServer::getTunnelStatus(QString tunnelName)
{
#ifdef Q_OS_LINUX
QProcess process;
QStringList commands;
commands << "ipsec" << "status" << QString("%1").arg(tunnelName);
process.start("sudo", commands);
if (!process.waitForStarted(1000))
{
qDebug().noquote() << "Could not stop ipsec tunnel\n";
return "";
}
else if (!process.waitForFinished(2000))
{
qDebug().noquote() << "Could not stop ipsec tunnel\n";
return "";
}
commands.clear();
QString status = process.readAll();
return status;
#endif
return QString();
}
bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
{
return KillSwitch::instance()->enablePeerTraffic(configStr);

View File

@@ -36,13 +36,20 @@ public:
virtual void StopRoutingIpv6() override;
virtual bool disableAllTraffic() override;
virtual bool addKillSwitchAllowedRange(QStringList ranges) override;
virtual bool removeKillSwitchAllowedRange(QStringList ranges) override;
virtual bool resetKillSwitchAllowedRange(QStringList ranges) override;
virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
virtual bool disableKillSwitch() override;
virtual bool refreshKillSwitch( bool enabled ) override;
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
virtual bool writeIPsecCaCert(QString cacert, QString uuid) override;
virtual bool writeIPsecPrivate(QString privKey, QString uuid) override;
virtual bool writeIPsecConfig(QString config) override;
virtual bool writeIPsecUserCert(QString usercert, QString uuid) override;
virtual bool writeIPsecPrivatePass(QString pass, QString host, QString uuid) override;
virtual bool stopIPsec(QString tunnelName) override;
virtual bool startIPsec(QString tunnelName) override;
virtual QString getTunnelStatus(QString tunnelName) override;
private:
int m_localpid = 0;

View File

@@ -189,21 +189,6 @@ bool KillSwitch::addAllowedRange(const QStringList &ranges) {
return resetAllowedRange(m_allowedRanges);
}
bool KillSwitch::removeAllowedRange(const QStringList &ranges) {
bool modified = false;
for (const QString &range : ranges) {
if (!range.isEmpty()) {
modified = modified || m_allowedRanges.removeAll(range) > 0;
}
}
if (!modified) {
return true;
}
return resetAllowedRange(m_allowedRanges);
}
bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) {
#ifdef Q_OS_WIN
InterfaceConfig config;

View File

@@ -19,7 +19,6 @@ public:
bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex);
bool resetAllowedRange(const QStringList &ranges);
bool addAllowedRange(const QStringList &ranges);
bool removeAllowedRange(const QStringList &ranges);
bool isStrictKillSwitchEnabled();
private: