mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 01:28:08 +03:00
Compare commits
15 Commits
feature/li
...
bugfix/str
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcef264559 | ||
|
|
f4a9bdd367 | ||
|
|
28424a9360 | ||
|
|
590412050f | ||
|
|
a1f865ddc7 | ||
|
|
f394bdb271 | ||
|
|
cab23b8e2e | ||
|
|
6bd13dccaa | ||
|
|
3abca41fe8 | ||
|
|
c66fa0c9ca | ||
|
|
2be594a2fe | ||
|
|
e3271f0bc9 | ||
|
|
166b45f5d0 | ||
|
|
e51af609ab | ||
|
|
16d92ddb7c |
@@ -3,11 +3,22 @@
|
||||
#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"
|
||||
@@ -66,10 +77,15 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
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());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -128,10 +144,15 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
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());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -362,3 +383,344 @@ 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
|
||||
@@ -27,6 +27,17 @@ 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;
|
||||
|
||||
@@ -34,7 +34,7 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButtonLayout.bottom
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: saveButton.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -37,7 +37,7 @@ PageType {
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButtonLayout.bottom
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
@@ -17,428 +17,414 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
id: backButtonLayout
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: 20
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if(backButton.enabled && backButton.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
id: fl
|
||||
anchors.top: backButtonLayout.bottom
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
contentHeight: content.implicitHeight
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
Column {
|
||||
id: content
|
||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
enabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||
BaseHeaderType {
|
||||
id: header
|
||||
|
||||
ListView {
|
||||
id: listview
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
|
||||
width: parent.width
|
||||
height: listview.contentItem.height
|
||||
headerText: qsTr("OpenVPN Settings")
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
interactive: false
|
||||
model: OpenVpnConfigModel
|
||||
|
||||
model: OpenVpnConfigModel
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
spacing: 0
|
||||
|
||||
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
|
||||
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
|
||||
TextFieldWithHeaderType {
|
||||
id: vpnAddressSubnetTextField
|
||||
|
||||
implicitWidth: listview.width
|
||||
implicitHeight: col.implicitHeight
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
enabled: listView.enabled
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
headerText: qsTr("VPN address subnet")
|
||||
textField.text: subnetAddress
|
||||
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== subnetAddress) {
|
||||
subnetAddress = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
spacing: 0
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("OpenVPN settings")
|
||||
}
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: vpnAddressSubnetTextField
|
||||
text: qsTr("Network protocol")
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
TransportProtoSelector {
|
||||
id: transportProtoSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: delegateItem.isEnabled
|
||||
rootWidth: root.width
|
||||
|
||||
headerText: qsTr("VPN address subnet")
|
||||
textField.text: subnetAddress
|
||||
enabled: isTransportProtoEditable
|
||||
|
||||
parentFlickable: fl
|
||||
currentIndex: {
|
||||
return transportProto === "tcp" ? 1 : 0
|
||||
}
|
||||
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== subnetAddress) {
|
||||
subnetAddress = textField.text
|
||||
}
|
||||
onCurrentIndexChanged: {
|
||||
if (transportProto === "tcp" && currentIndex === 0) {
|
||||
transportProto = "udp"
|
||||
} else if (transportProto === "udp" && currentIndex === 1) {
|
||||
transportProto = "tcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: portTextField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 40
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: listView.enabled
|
||||
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
ParagraphTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
|
||||
text: qsTr("Network protocol")
|
||||
}
|
||||
|
||||
TransportProtoSelector {
|
||||
id: transportProtoSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
rootWidth: root.width
|
||||
|
||||
enabled: isTransportProtoEditable
|
||||
|
||||
currentIndex: {
|
||||
return transportProto === "tcp" ? 1 : 0
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (transportProto === "tcp" && currentIndex === 0) {
|
||||
transportProto = "udp"
|
||||
} else if (transportProto === "udp" && currentIndex === 1) {
|
||||
transportProto = "tcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: portTextField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 40
|
||||
parentFlickable: fl
|
||||
|
||||
enabled: delegateItem.isEnabled
|
||||
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== port) {
|
||||
port = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
checkEmptyText: true
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: autoNegotiateEncryprionSwitcher
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 24
|
||||
parentFlickable: fl
|
||||
|
||||
text: qsTr("Auto-negotiate encryption")
|
||||
checked: autoNegotiateEncryprion
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked !== autoNegotiateEncryprion) {
|
||||
autoNegotiateEncryprion = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: hashDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: cipherDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
|
||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||
|
||||
descriptionText: qsTr("Cipher")
|
||||
headerText: qsTr("Cipher")
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentRect
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.preferredHeight: checkboxLayout.implicitHeight
|
||||
color: AmneziaStyle.color.onyxBlack
|
||||
radius: 16
|
||||
|
||||
Connections {
|
||||
target: tlsAuthCheckBox
|
||||
enabled: !GC.isMobile()
|
||||
|
||||
function onFocusChanged() {
|
||||
if (tlsAuthCheckBox.activeFocus) {
|
||||
fl.ensureVisible(contentRect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: checkboxLayout
|
||||
|
||||
anchors.fill: parent
|
||||
CheckBoxType {
|
||||
id: tlsAuthCheckBox
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("TLS auth")
|
||||
checked: tlsAuth
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked !== tlsAuth) {
|
||||
console.log("tlsAuth changed to: " + checked)
|
||||
tlsAuth = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
CheckBoxType {
|
||||
id: blockDnsCheckBox
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Block DNS requests outside of VPN")
|
||||
checked: blockDns
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked !== blockDns) {
|
||||
blockDns = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: cipherDropDown
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: !autoNegotiateEncryprionSwitcher.checked
|
||||
|
||||
descriptionText: qsTr("Cipher")
|
||||
headerText: qsTr("Cipher")
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentRect
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
Layout.preferredHeight: checkboxLayout.implicitHeight
|
||||
color: AmneziaStyle.color.onyxBlack
|
||||
radius: 16
|
||||
|
||||
ColumnLayout {
|
||||
id: checkboxLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
CheckBoxType {
|
||||
id: tlsAuthCheckBox
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("TLS auth")
|
||||
checked: tlsAuth
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked !== tlsAuth) {
|
||||
console.log("tlsAuth changed to: " + checked)
|
||||
tlsAuth = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
CheckBoxType {
|
||||
id: blockDnsCheckBox
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Block DNS requests outside of VPN")
|
||||
checked: blockDns
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked !== blockDns) {
|
||||
blockDns = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: additionalClientCommandsSwitcher
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
checked: additionalClientCommands !== ""
|
||||
|
||||
text: qsTr("Additional client configuration commands")
|
||||
|
||||
onCheckedChanged: {
|
||||
if (!checked) {
|
||||
additionalClientCommands = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextAreaType {
|
||||
id: additionalClientCommandsTextArea
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
visible: additionalClientCommandsSwitcher.checked
|
||||
|
||||
textAreaText: additionalClientCommands
|
||||
placeholderText: qsTr("Commands:")
|
||||
|
||||
textArea.onEditingFinished: {
|
||||
if (additionalClientCommands !== textAreaText) {
|
||||
additionalClientCommands = textAreaText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
id: additionalServerCommandsSwitcher
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
checked: additionalServerCommands !== ""
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,6 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: delegateItem.isEnabled
|
||||
|
||||
headerText: qsTr("VPN address subnet")
|
||||
textField.text: subnetAddress
|
||||
|
||||
@@ -87,8 +85,6 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
enabled: delegateItem.isEnabled
|
||||
|
||||
headerText: qsTr("Port")
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
|
||||
@@ -43,8 +43,6 @@ PageType {
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
visible: isVisible
|
||||
|
||||
@@ -68,8 +66,6 @@ PageType {
|
||||
|
||||
visible: GC.isDesktop()
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Close application")
|
||||
leftImageSource: "qrc:/images/controls/x-circle.svg"
|
||||
|
||||
@@ -66,6 +66,13 @@ 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
|
||||
@@ -96,13 +103,6 @@ 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,10 +139,6 @@ PageType {
|
||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BasicButtonType {
|
||||
id: saveButton
|
||||
|
||||
@@ -64,12 +64,15 @@ 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() {
|
||||
SettingsController.strictKillSwitchEnabled = false
|
||||
if (SettingsController.strictKillSwitchEnabled) {
|
||||
SettingsController.strictKillSwitchEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: this.clicked()
|
||||
@@ -84,15 +87,18 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
visible: false
|
||||
enabled: false
|
||||
// enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected
|
||||
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")
|
||||
|
||||
@@ -18,6 +18,8 @@ PageType {
|
||||
|
||||
signal lastItemTabClickedSignal()
|
||||
|
||||
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
|
||||
|
||||
Connections {
|
||||
target: InstallController
|
||||
|
||||
@@ -59,15 +61,13 @@ PageType {
|
||||
target: ServersModel
|
||||
|
||||
function onProcessedServerIndexChanged() {
|
||||
listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
|
||||
root.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: true
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
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: true
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
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: true
|
||||
property bool isVisible: root.isServerWithWriteAccess
|
||||
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")
|
||||
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium
|
||||
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
|
||||
readonly property string description: ""
|
||||
readonly property var tColor: AmneziaStyle.color.vibrantRed
|
||||
|
||||
@@ -161,10 +161,4 @@ PageType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShareConnectionDrawer {
|
||||
id: shareConnectionDrawer
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ 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) );
|
||||
|
||||
@@ -189,6 +189,11 @@ 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();
|
||||
|
||||
@@ -36,6 +36,7 @@ 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;
|
||||
|
||||
@@ -189,6 +189,21 @@ 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;
|
||||
|
||||
@@ -19,6 +19,7 @@ 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:
|
||||
|
||||
Reference in New Issue
Block a user