mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
DNS update
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
# User settings
|
||||
*.user
|
||||
|
||||
# Gateway configs (contains sensitive endpoints)
|
||||
gateway.json
|
||||
client/gateway.json
|
||||
macOSPackage/
|
||||
AmneziaVPN.dmg
|
||||
AmneziaVPN.exe
|
||||
|
||||
@@ -227,5 +227,16 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Copy gateway.json to build directory if exists
|
||||
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/gateway.json")
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_LIST_DIR}/gateway.json
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMENT "Copying gateway.json to build directory"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
qt_finalize_target(${PROJECT})
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#include "gatewayController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
#include <QHostAddress>
|
||||
#include <QDebug>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
@@ -50,6 +55,90 @@ namespace
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
}
|
||||
|
||||
// Parse TransportsConfig from JSON
|
||||
TransportsConfig TransportsConfig::fromJson(const QJsonObject &json)
|
||||
{
|
||||
TransportsConfig config;
|
||||
|
||||
// Parse primary transport
|
||||
QString primaryStr = json.value("primary").toString("http").toLower();
|
||||
if (primaryStr == "http") {
|
||||
config.primary = PrimaryTransport::Http;
|
||||
} else if (primaryStr == "dns_udp" || primaryStr == "udp") {
|
||||
config.primary = PrimaryTransport::DnsUdp;
|
||||
} else if (primaryStr == "dns_tcp" || primaryStr == "tcp") {
|
||||
config.primary = PrimaryTransport::DnsTcp;
|
||||
} else if (primaryStr == "dns_dot" || primaryStr == "dot") {
|
||||
config.primary = PrimaryTransport::DnsDot;
|
||||
} else if (primaryStr == "dns_doh" || primaryStr == "doh") {
|
||||
config.primary = PrimaryTransport::DnsDoh;
|
||||
} else if (primaryStr == "dns_doq" || primaryStr == "doq") {
|
||||
config.primary = PrimaryTransport::DnsDoq;
|
||||
}
|
||||
|
||||
// Parse retry settings
|
||||
config.retryCount = json.value("retry_count").toInt(3);
|
||||
config.timeoutMs = json.value("timeout_ms").toInt(10000);
|
||||
|
||||
// Parse HTTP config
|
||||
if (json.contains("http")) {
|
||||
QJsonObject httpObj = json["http"].toObject();
|
||||
config.httpEnabled = httpObj.value("enabled").toBool(true);
|
||||
config.httpEndpoint = httpObj.value("endpoint").toString();
|
||||
}
|
||||
|
||||
// Parse DNS transports (each with its own server/domain)
|
||||
if (json.contains("dns_transports")) {
|
||||
QJsonArray transportsArray = json["dns_transports"].toArray();
|
||||
for (const auto &transportVal : transportsArray) {
|
||||
QJsonObject transportObj = transportVal.toObject();
|
||||
DnsTransportEntry entry;
|
||||
|
||||
// Each transport has its own server and domain
|
||||
entry.server = transportObj.value("server").toString();
|
||||
entry.domain = transportObj.value("domain").toString();
|
||||
entry.port = static_cast<quint16>(transportObj.value("port").toInt(15353));
|
||||
entry.dohPath = transportObj.value("path").toString("/dns-query");
|
||||
|
||||
QString typeStr = transportObj.value("type").toString().toLower();
|
||||
if (typeStr == "udp") {
|
||||
entry.type = NetworkUtilities::DnsTransport::Udp;
|
||||
if (entry.port == 15353 && !transportObj.contains("port")) {
|
||||
entry.port = 15353;
|
||||
}
|
||||
} else if (typeStr == "tcp") {
|
||||
entry.type = NetworkUtilities::DnsTransport::Tcp;
|
||||
if (entry.port == 15353 && !transportObj.contains("port")) {
|
||||
entry.port = 15353;
|
||||
}
|
||||
} else if (typeStr == "dot" || typeStr == "tls") {
|
||||
entry.type = NetworkUtilities::DnsTransport::Tls;
|
||||
if (!transportObj.contains("port")) {
|
||||
entry.port = 8853;
|
||||
}
|
||||
} else if (typeStr == "doh" || typeStr == "https") {
|
||||
entry.type = NetworkUtilities::DnsTransport::Https;
|
||||
if (!transportObj.contains("port")) {
|
||||
entry.port = 443;
|
||||
}
|
||||
} else if (typeStr == "doq" || typeStr == "quic") {
|
||||
entry.type = NetworkUtilities::DnsTransport::Quic;
|
||||
if (!transportObj.contains("port")) {
|
||||
entry.port = 8853;
|
||||
}
|
||||
} else {
|
||||
continue; // Skip unknown transport
|
||||
}
|
||||
|
||||
if (entry.isValid()) {
|
||||
config.dnsTransports.append(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -81,6 +170,296 @@ void GatewayController::setDnsServer(const QString &dnsServer, const QString &ba
|
||||
<< "Transport:" << transportName << "Port:" << port;
|
||||
}
|
||||
|
||||
void GatewayController::setTransportsConfig(const TransportsConfig &config)
|
||||
{
|
||||
m_transportsConfig = config;
|
||||
|
||||
// Update legacy fields for backward compatibility
|
||||
if (!config.httpEndpoint.isEmpty()) {
|
||||
m_gatewayEndpoint = config.httpEndpoint;
|
||||
}
|
||||
|
||||
// Update timeout from config
|
||||
if (config.timeoutMs > 0) {
|
||||
m_requestTimeoutMsecs = config.timeoutMs;
|
||||
}
|
||||
|
||||
qDebug() << "[Transport] Config set: HTTP enabled=" << config.httpEnabled
|
||||
<< "endpoint=" << config.httpEndpoint
|
||||
<< "DNS transports=" << config.dnsTransports.size()
|
||||
<< "primary=" << static_cast<int>(config.primary)
|
||||
<< "retry=" << config.retryCount
|
||||
<< "timeout=" << config.timeoutMs;
|
||||
}
|
||||
|
||||
bool GatewayController::loadTransportsConfig(const QString &filePath, const QString &envVarName)
|
||||
{
|
||||
// Try environment variable first
|
||||
QString envValue = QProcessEnvironment::systemEnvironment().value(envVarName);
|
||||
if (!envValue.isEmpty()) {
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(envValue.toUtf8(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
setTransportsConfig(TransportsConfig::fromJson(doc.object()));
|
||||
qDebug() << "[Transport] Loaded config from env:" << envVarName;
|
||||
return true;
|
||||
}
|
||||
qWarning() << "[Transport] Failed to parse env" << envVarName << ":" << parseError.errorString();
|
||||
}
|
||||
|
||||
// Try file
|
||||
QFile file(filePath);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray data = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
setTransportsConfig(TransportsConfig::fromJson(doc.object()));
|
||||
qDebug() << "[Transport] Loaded config from file:" << filePath;
|
||||
return true;
|
||||
}
|
||||
qWarning() << "[Transport] Failed to parse file" << filePath << ":" << parseError.errorString();
|
||||
}
|
||||
|
||||
qDebug() << "[Transport] No config found, using defaults";
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::postParallel(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
if (!m_transportsConfig.isValid()) {
|
||||
qWarning() << "[Transport] Invalid config, falling back to HTTP only";
|
||||
return post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
// Prepare encrypted request once (skip DNS resolve for DNS tunneling)
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload, true);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
// Extract endpoint name for DNS tunneling
|
||||
QString endpointName = endpoint;
|
||||
endpointName.remove("%1");
|
||||
if (endpointName.startsWith("v1/")) {
|
||||
endpointName = endpointName.mid(3);
|
||||
}
|
||||
if (endpointName.endsWith("/")) {
|
||||
endpointName.chop(1);
|
||||
}
|
||||
|
||||
// Helper: find DNS transport by type
|
||||
auto findDnsTransport = [&](NetworkUtilities::DnsTransport type) -> const DnsTransportEntry* {
|
||||
for (const auto &t : m_transportsConfig.dnsTransports) {
|
||||
if (t.type == type && t.isValid()) return &t;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
// Helper: try HTTP transport
|
||||
auto tryHttp = [&]() -> ErrorCode {
|
||||
if (!m_transportsConfig.httpEnabled) return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
|
||||
qDebug() << "[Transport] PRIMARY: Trying HTTP";
|
||||
EncryptedRequestData httpRequestData = prepareRequest(endpoint, apiPayload, false);
|
||||
if (httpRequestData.errorCode != ErrorCode::NoError) return httpRequestData.errorCode;
|
||||
|
||||
QNetworkAccessManager nam;
|
||||
QNetworkReply *reply = nam.post(httpRequestData.request, httpRequestData.requestBody);
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
QByteArray encryptedBody = reply->readAll();
|
||||
auto replyError = reply->error();
|
||||
reply->deleteLater();
|
||||
|
||||
if (replyError != QNetworkReply::NoError || encryptedBody.isEmpty()) {
|
||||
qDebug() << "[Transport] PRIMARY HTTP failed";
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedBody, httpRequestData.key, httpRequestData.iv, "", httpRequestData.salt);
|
||||
qDebug() << "[Transport] PRIMARY HTTP succeeded";
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) {
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper: try DNS transport
|
||||
auto tryDns = [&](const DnsTransportEntry &transport) -> ErrorCode {
|
||||
QString transportName;
|
||||
switch (transport.type) {
|
||||
case NetworkUtilities::DnsTransport::Udp: transportName = "UDP"; break;
|
||||
case NetworkUtilities::DnsTransport::Tcp: transportName = "TCP"; break;
|
||||
case NetworkUtilities::DnsTransport::Tls: transportName = "DoT"; break;
|
||||
case NetworkUtilities::DnsTransport::Https: transportName = "DoH"; break;
|
||||
case NetworkUtilities::DnsTransport::Quic: transportName = "DoQ"; break;
|
||||
}
|
||||
|
||||
qDebug() << "[Transport] PRIMARY: Trying DNS" << transportName;
|
||||
QByteArray dnsResponse = NetworkUtilities::sendViaDnsTunnel(
|
||||
encRequestData.requestBody, endpointName, transport.domain,
|
||||
transport.server, transport.type, transport.port, m_requestTimeoutMsecs, transport.dohPath);
|
||||
|
||||
if (dnsResponse.isEmpty()) {
|
||||
qDebug() << "[Transport] PRIMARY DNS" << transportName << "failed";
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(dnsResponse, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
qDebug() << "[Transport] PRIMARY DNS" << transportName << "succeeded";
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) {
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
};
|
||||
|
||||
// === STEP 1: Try PRIMARY transport first ===
|
||||
qDebug() << "[Transport] Trying primary transport:" << static_cast<int>(m_transportsConfig.primary);
|
||||
|
||||
ErrorCode primaryResult = ErrorCode::AmneziaServiceConnectionFailed;
|
||||
switch (m_transportsConfig.primary) {
|
||||
case PrimaryTransport::Http:
|
||||
primaryResult = tryHttp();
|
||||
break;
|
||||
case PrimaryTransport::DnsUdp:
|
||||
if (auto t = findDnsTransport(NetworkUtilities::DnsTransport::Udp)) primaryResult = tryDns(*t);
|
||||
break;
|
||||
case PrimaryTransport::DnsTcp:
|
||||
if (auto t = findDnsTransport(NetworkUtilities::DnsTransport::Tcp)) primaryResult = tryDns(*t);
|
||||
break;
|
||||
case PrimaryTransport::DnsDot:
|
||||
if (auto t = findDnsTransport(NetworkUtilities::DnsTransport::Tls)) primaryResult = tryDns(*t);
|
||||
break;
|
||||
case PrimaryTransport::DnsDoh:
|
||||
if (auto t = findDnsTransport(NetworkUtilities::DnsTransport::Https)) primaryResult = tryDns(*t);
|
||||
break;
|
||||
case PrimaryTransport::DnsDoq:
|
||||
if (auto t = findDnsTransport(NetworkUtilities::DnsTransport::Quic)) primaryResult = tryDns(*t);
|
||||
break;
|
||||
}
|
||||
|
||||
if (primaryResult == ErrorCode::NoError) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
// === STEP 2: Primary failed — launch ALL other transports in parallel ===
|
||||
qDebug() << "[Transport] Primary failed, launching parallel fallback";
|
||||
|
||||
std::atomic<bool> gotSuccess{false};
|
||||
QByteArray successResult;
|
||||
QString successTransport;
|
||||
QMutex resultMutex;
|
||||
QList<QFuture<void>> futures;
|
||||
|
||||
// HTTP (if not primary and enabled)
|
||||
if (m_transportsConfig.primary != PrimaryTransport::Http && m_transportsConfig.httpEnabled) {
|
||||
auto httpFuture = QtConcurrent::run([&]() {
|
||||
if (gotSuccess.load()) return;
|
||||
|
||||
qDebug() << "[Transport] FALLBACK: Trying HTTP";
|
||||
EncryptedRequestData httpRequestData = prepareRequest(endpoint, apiPayload, false);
|
||||
if (httpRequestData.errorCode != ErrorCode::NoError) return;
|
||||
|
||||
QNetworkAccessManager nam;
|
||||
QNetworkReply *reply = nam.post(httpRequestData.request, httpRequestData.requestBody);
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
if (gotSuccess.load()) { reply->deleteLater(); return; }
|
||||
|
||||
QByteArray encryptedBody = reply->readAll();
|
||||
auto replyError = reply->error();
|
||||
reply->deleteLater();
|
||||
|
||||
if (replyError != QNetworkReply::NoError || encryptedBody.isEmpty()) return;
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray decrypted = blockCipher.decryptAesBlockCipher(encryptedBody, httpRequestData.key, httpRequestData.iv, "", httpRequestData.salt);
|
||||
if (!gotSuccess.exchange(true)) {
|
||||
QMutexLocker lock(&resultMutex);
|
||||
successResult = decrypted;
|
||||
successTransport = "HTTP";
|
||||
}
|
||||
} catch (...) {}
|
||||
});
|
||||
futures.append(httpFuture);
|
||||
}
|
||||
|
||||
// DNS transports (skip the one that was primary)
|
||||
for (const auto &transport : m_transportsConfig.dnsTransports) {
|
||||
if (!transport.isValid()) continue;
|
||||
|
||||
// Skip if this was primary
|
||||
bool wasPrimary = false;
|
||||
switch (m_transportsConfig.primary) {
|
||||
case PrimaryTransport::DnsUdp: wasPrimary = (transport.type == NetworkUtilities::DnsTransport::Udp); break;
|
||||
case PrimaryTransport::DnsTcp: wasPrimary = (transport.type == NetworkUtilities::DnsTransport::Tcp); break;
|
||||
case PrimaryTransport::DnsDot: wasPrimary = (transport.type == NetworkUtilities::DnsTransport::Tls); break;
|
||||
case PrimaryTransport::DnsDoh: wasPrimary = (transport.type == NetworkUtilities::DnsTransport::Https); break;
|
||||
case PrimaryTransport::DnsDoq: wasPrimary = (transport.type == NetworkUtilities::DnsTransport::Quic); break;
|
||||
default: break;
|
||||
}
|
||||
if (wasPrimary) continue;
|
||||
|
||||
auto dnsFuture = QtConcurrent::run([&, transport]() {
|
||||
if (gotSuccess.load()) return;
|
||||
|
||||
QString transportName;
|
||||
switch (transport.type) {
|
||||
case NetworkUtilities::DnsTransport::Udp: transportName = "UDP"; break;
|
||||
case NetworkUtilities::DnsTransport::Tcp: transportName = "TCP"; break;
|
||||
case NetworkUtilities::DnsTransport::Tls: transportName = "DoT"; break;
|
||||
case NetworkUtilities::DnsTransport::Https: transportName = "DoH"; break;
|
||||
case NetworkUtilities::DnsTransport::Quic: transportName = "DoQ"; break;
|
||||
}
|
||||
|
||||
qDebug() << "[Transport] FALLBACK: Trying DNS" << transportName;
|
||||
QByteArray dnsResponse = NetworkUtilities::sendViaDnsTunnel(
|
||||
encRequestData.requestBody, endpointName, transport.domain,
|
||||
transport.server, transport.type, transport.port, m_requestTimeoutMsecs, transport.dohPath);
|
||||
|
||||
if (dnsResponse.isEmpty()) return;
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray decrypted = blockCipher.decryptAesBlockCipher(dnsResponse, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
||||
if (!gotSuccess.exchange(true)) {
|
||||
QMutexLocker lock(&resultMutex);
|
||||
successResult = decrypted;
|
||||
successTransport = "DNS-" + transportName;
|
||||
}
|
||||
} catch (...) {}
|
||||
});
|
||||
futures.append(dnsFuture);
|
||||
}
|
||||
|
||||
// CRITICAL: Wait for ALL futures to complete to prevent use-after-free
|
||||
// (lambdas capture references to local variables like resultMutex, successResult)
|
||||
for (auto &future : futures) {
|
||||
future.waitForFinished();
|
||||
}
|
||||
|
||||
if (gotSuccess.load()) {
|
||||
responseBody = successResult;
|
||||
qDebug() << "[Transport] FALLBACK success via" << successTransport;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
qDebug() << "[Transport] All transports failed";
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
QString GatewayController::resolveGatewayHostname(const QString &hostname)
|
||||
{
|
||||
if (m_dnsServer.isEmpty()) {
|
||||
@@ -116,8 +495,8 @@ ErrorCode GatewayController::postViaDns(const QString &endpoint, const QJsonObje
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
// Prepare encrypted request
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
// Prepare encrypted request (skip DNS resolve - we send directly to m_dnsServer)
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload, true);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
@@ -169,7 +548,7 @@ ErrorCode GatewayController::postViaDns(const QString &endpoint, const QJsonObje
|
||||
}
|
||||
}
|
||||
|
||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload, bool skipDnsResolve)
|
||||
{
|
||||
EncryptedRequestData encRequestData;
|
||||
encRequestData.errorCode = ErrorCode::NoError;
|
||||
@@ -183,13 +562,14 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
|
||||
// DNS резолв через TCP/UDP
|
||||
// DNS резолв через TCP/UDP (пропускаем для DNS tunneling — там запрос идёт напрямую к DNS серверу)
|
||||
QString finalGatewayEndpoint = m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl;
|
||||
QUrl gatewayUrl(finalGatewayEndpoint);
|
||||
QString hostname = gatewayUrl.host();
|
||||
|
||||
// Проверяем, нужно ли резолвить (если это не IP адрес и не localhost)
|
||||
if (!hostname.isEmpty() &&
|
||||
if (!skipDnsResolve &&
|
||||
!hostname.isEmpty() &&
|
||||
hostname != "localhost" &&
|
||||
!NetworkUtilities::checkIPv4Format(hostname) &&
|
||||
QHostAddress(hostname).isNull()) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
@@ -15,6 +17,33 @@
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
// NEW: Configuration for a single DNS transport (each can have its own server/domain)
|
||||
struct DnsTransportEntry {
|
||||
NetworkUtilities::DnsTransport type = NetworkUtilities::DnsTransport::Udp;
|
||||
QString server; // DNS server IP
|
||||
QString domain; // Base domain for tunneling
|
||||
quint16 port = 15353;
|
||||
QString dohPath = "/dns-query";
|
||||
|
||||
bool isValid() const { return !server.isEmpty() && !domain.isEmpty(); }
|
||||
};
|
||||
|
||||
// NEW: Primary transport type
|
||||
enum class PrimaryTransport { Http, DnsUdp, DnsTcp, DnsDot, DnsDoh, DnsDoq };
|
||||
|
||||
// NEW: Full transports configuration
|
||||
struct TransportsConfig {
|
||||
PrimaryTransport primary = PrimaryTransport::Http;
|
||||
bool httpEnabled = true;
|
||||
QString httpEndpoint;
|
||||
QList<DnsTransportEntry> dnsTransports;
|
||||
int retryCount = 3;
|
||||
int timeoutMs = 10000;
|
||||
|
||||
bool isValid() const { return httpEnabled || !dnsTransports.isEmpty(); }
|
||||
static TransportsConfig fromJson(const QJsonObject &json);
|
||||
};
|
||||
|
||||
class GatewayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -32,6 +61,13 @@ public:
|
||||
|
||||
// DNS tunneling - send request via DNS transport
|
||||
amnezia::ErrorCode postViaDns(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
|
||||
// NEW: Load config from file or environment variable
|
||||
bool loadTransportsConfig(const QString &filePath, const QString &envVarName = "AMNEZIA_GATEWAY");
|
||||
void setTransportsConfig(const TransportsConfig &config);
|
||||
|
||||
// NEW: Parallel request via all configured transports (primary first, then others)
|
||||
amnezia::ErrorCode postParallel(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
|
||||
private:
|
||||
struct EncryptedRequestData
|
||||
@@ -50,7 +86,7 @@ private:
|
||||
bool isDecryptionSuccessful;
|
||||
};
|
||||
|
||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload, bool skipDnsResolve = false);
|
||||
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
|
||||
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
|
||||
QString resolveGatewayHostname(const QString &hostname);
|
||||
@@ -73,12 +109,15 @@ private:
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
|
||||
// DNS transport settings
|
||||
// DNS transport settings (for individual transport)
|
||||
QString m_dnsServer;
|
||||
QString m_dnsBaseDomain;
|
||||
NetworkUtilities::DnsTransport m_dnsTransport = NetworkUtilities::DnsTransport::Udp;
|
||||
quint16 m_dnsPort = 15353;
|
||||
QString m_dohEndpoint = "/dns-query";
|
||||
|
||||
// NEW: Full transports configuration
|
||||
TransportsConfig m_transportsConfig;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
#include <QThread>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -1147,37 +1149,44 @@ namespace {
|
||||
|
||||
int pos = 12;
|
||||
|
||||
// Skip questions
|
||||
for (int i = 0; i < qdCount && pos < response.size(); i++) {
|
||||
while (pos < response.size() && data[pos] != 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) { pos += 2; break; }
|
||||
pos += data[pos] + 1;
|
||||
// Helper lambda to safely skip DNS name with bounds checking
|
||||
auto skipDnsName = [&]() -> bool {
|
||||
int maxLabels = 128; // Prevent infinite loops
|
||||
while (pos < response.size() && data[pos] != 0 && maxLabels-- > 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) {
|
||||
pos += 2;
|
||||
return pos <= response.size();
|
||||
}
|
||||
int labelLen = data[pos];
|
||||
if (pos + 1 + labelLen > response.size()) return false; // Bounds check
|
||||
pos += labelLen + 1;
|
||||
}
|
||||
if (pos < response.size() && data[pos] == 0) pos++;
|
||||
return pos <= response.size();
|
||||
};
|
||||
|
||||
// Skip questions
|
||||
for (int i = 0; i < qdCount && pos < response.size(); i++) {
|
||||
if (!skipDnsName()) return meta;
|
||||
if (pos + 4 > response.size()) return meta;
|
||||
pos += 4; // QTYPE + QCLASS
|
||||
}
|
||||
|
||||
// Skip answers
|
||||
for (int i = 0; i < anCount && pos < response.size(); i++) {
|
||||
while (pos < response.size() && data[pos] != 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) { pos += 2; break; }
|
||||
pos += data[pos] + 1;
|
||||
}
|
||||
if (pos < response.size() && data[pos] == 0) pos++;
|
||||
if (pos + 10 > response.size()) break;
|
||||
if (!skipDnsName()) return meta;
|
||||
if (pos + 10 > response.size()) return meta;
|
||||
quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
|
||||
if (pos + 10 + rdlen > response.size()) return meta;
|
||||
pos += 10 + rdlen;
|
||||
}
|
||||
|
||||
// Skip authority
|
||||
for (int i = 0; i < nsCount && pos < response.size(); i++) {
|
||||
while (pos < response.size() && data[pos] != 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) { pos += 2; break; }
|
||||
pos += data[pos] + 1;
|
||||
}
|
||||
if (pos < response.size() && data[pos] == 0) pos++;
|
||||
if (pos + 10 > response.size()) break;
|
||||
if (!skipDnsName()) return meta;
|
||||
if (pos + 10 > response.size()) return meta;
|
||||
quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
|
||||
if (pos + 10 + rdlen > response.size()) return meta;
|
||||
pos += 10 + rdlen;
|
||||
}
|
||||
|
||||
@@ -1187,17 +1196,14 @@ namespace {
|
||||
if (pos < response.size() && data[pos] == 0) {
|
||||
pos++; // Root label for OPT
|
||||
} else {
|
||||
while (pos < response.size() && data[pos] != 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) { pos += 2; break; }
|
||||
pos += data[pos] + 1;
|
||||
}
|
||||
if (pos < response.size() && data[pos] == 0) pos++;
|
||||
if (!skipDnsName()) return meta;
|
||||
}
|
||||
|
||||
if (pos + 10 > response.size()) break;
|
||||
if (pos + 10 > response.size()) return meta;
|
||||
|
||||
quint16 rtype = (data[pos] << 8) | data[pos + 1];
|
||||
quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
|
||||
if (pos + 10 + rdlen > response.size()) return meta;
|
||||
pos += 10;
|
||||
|
||||
if (rtype == 41 && rdlen > 0) { // OPT record
|
||||
@@ -1295,24 +1301,47 @@ namespace {
|
||||
qDebug() << "[DNS Tunnel] Response header: id=" << transactionId << "flags=" << Qt::hex << flags
|
||||
<< "questions=" << qdCount << "answers=" << anCount << "authority=" << nsCount << "additional=" << arCount;
|
||||
|
||||
// Validate QR bit - must be 1 for response (bit 15 of flags)
|
||||
if ((flags & 0x8000) == 0) {
|
||||
qDebug() << "[DNS Tunnel] Invalid response: QR bit not set (not a response)";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Check for errors (RCODE in lower 4 bits of flags)
|
||||
quint8 rcode = flags & 0x0F;
|
||||
if (rcode != 0) {
|
||||
qDebug() << "[DNS Tunnel] Response error, RCODE:" << rcode;
|
||||
}
|
||||
|
||||
// Sanity check on counts to prevent excessive iteration
|
||||
if (anCount > 100 || qdCount > 10) {
|
||||
qDebug() << "[DNS Tunnel] Suspicious counts, likely garbage data";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Helper lambda to safely skip DNS name with bounds checking
|
||||
auto skipDnsName = [&]() -> bool {
|
||||
int maxLabels = 128; // Prevent infinite loops
|
||||
while (pos < response.size() && data[pos] != 0 && maxLabels-- > 0) {
|
||||
if ((data[pos] & 0xC0) == 0xC0) {
|
||||
pos += 2;
|
||||
return pos <= response.size();
|
||||
}
|
||||
int labelLen = data[pos];
|
||||
if (pos + 1 + labelLen > response.size()) return false;
|
||||
pos += labelLen + 1;
|
||||
}
|
||||
if (pos < response.size() && data[pos] == 0) pos++;
|
||||
return pos <= response.size();
|
||||
};
|
||||
|
||||
// Skip question section
|
||||
for (int i = 0; i < qdCount && pos < response.size(); i++) {
|
||||
// Skip name
|
||||
while (pos < response.size()) {
|
||||
quint8 len = data[pos++];
|
||||
if (len == 0) break;
|
||||
if ((len & 0xC0) == 0xC0) {
|
||||
pos++; // Skip pointer byte
|
||||
break;
|
||||
}
|
||||
pos += len;
|
||||
if (!skipDnsName()) {
|
||||
qDebug() << "[DNS Tunnel] Failed to skip question name";
|
||||
return QByteArray();
|
||||
}
|
||||
if (pos + 4 > response.size()) return QByteArray();
|
||||
pos += 4; // Skip QTYPE and QCLASS
|
||||
}
|
||||
|
||||
@@ -1321,15 +1350,9 @@ namespace {
|
||||
// Read answer section - looking for TXT records
|
||||
QByteArray combinedTxt;
|
||||
for (int i = 0; i < anCount && pos < response.size(); i++) {
|
||||
// Skip name (handle compression)
|
||||
while (pos < response.size()) {
|
||||
quint8 len = data[pos++];
|
||||
if (len == 0) break;
|
||||
if ((len & 0xC0) == 0xC0) {
|
||||
pos++; // Skip pointer byte
|
||||
break;
|
||||
}
|
||||
pos += len;
|
||||
if (!skipDnsName()) {
|
||||
qDebug() << "[DNS Tunnel] Failed to skip answer name at pos=" << pos;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos + 10 > response.size()) {
|
||||
@@ -1344,13 +1367,21 @@ namespace {
|
||||
|
||||
qDebug() << "[DNS Tunnel] Answer" << i << ": type=" << rtype << "class=" << rclass << "ttl=" << ttl << "rdlen=" << rdlength;
|
||||
|
||||
// Bounds check for rdlength
|
||||
if (pos + rdlength > response.size()) {
|
||||
qDebug() << "[DNS Tunnel] rdlength exceeds buffer, truncating";
|
||||
break;
|
||||
}
|
||||
|
||||
if (rtype == 16) { // TXT record
|
||||
int rdEnd = pos + rdlength;
|
||||
while (pos < rdEnd && pos < response.size()) {
|
||||
quint8 txtLen = data[pos++];
|
||||
if (txtLen > 0 && pos + txtLen <= rdEnd) {
|
||||
if (txtLen > 0 && pos + txtLen <= rdEnd && pos + txtLen <= response.size()) {
|
||||
combinedTxt.append(reinterpret_cast<const char*>(data + pos), txtLen);
|
||||
pos += txtLen;
|
||||
} else {
|
||||
break; // Invalid TXT length
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1396,9 +1427,9 @@ QByteArray NetworkUtilities::sendViaDnsTunnel(const QByteArray &payload, const Q
|
||||
case DnsTransport::Https:
|
||||
return sendViaDnsTunnelHttps(payload, queryName, dnsServer, port, dohEndpoint, timeoutMsecs);
|
||||
case DnsTransport::Quic:
|
||||
// DoQ uses QUIC - not yet implemented, fallback to chunked UDP
|
||||
qDebug() << "[DNS Tunnel] DoQ not yet implemented, falling back to UDP";
|
||||
return sendViaDnsTunnelUdpChunked(payload, queryName, dnsServer, port, timeoutMsecs);
|
||||
// DoQ uses QUIC - not yet implemented
|
||||
qDebug() << "[DNS Tunnel] DoQ not yet implemented";
|
||||
return QByteArray(); // Return empty to trigger fallback to other transports
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
@@ -1593,45 +1624,49 @@ QByteArray NetworkUtilities::sendViaDnsTunnelTls(const QByteArray &payload, cons
|
||||
|
||||
qDebug() << "[DNS Tunnel DoT] Sent" << bytesWritten << "bytes";
|
||||
|
||||
// Wait for response
|
||||
QEventLoop loop;
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
timer.setInterval(timeoutMsecs);
|
||||
|
||||
QByteArray response;
|
||||
bool responseReceived = false;
|
||||
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
QObject::connect(&socket, &QSslSocket::readyRead, [&]() {
|
||||
if (socket.bytesAvailable() >= 2 && response.isEmpty()) {
|
||||
QByteArray lengthBytes = socket.read(2);
|
||||
if (lengthBytes.size() == 2) {
|
||||
quint16 responseLength = qFromBigEndian<quint16>(*reinterpret_cast<const quint16*>(lengthBytes.constData()));
|
||||
while (socket.bytesAvailable() < responseLength) {
|
||||
if (!socket.waitForReadyRead(timeoutMsecs / 2)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (socket.bytesAvailable() >= responseLength) {
|
||||
response = socket.read(responseLength);
|
||||
responseReceived = true;
|
||||
loop.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Synchronous read: first get 2-byte length prefix
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
loop.exec();
|
||||
timer.stop();
|
||||
socket.close();
|
||||
|
||||
if (!responseReceived || response.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel DoT] No response received (timeout)";
|
||||
while (socket.bytesAvailable() < 2) {
|
||||
int remaining = timeoutMsecs - timer.elapsed();
|
||||
if (remaining <= 0 || !socket.waitForReadyRead(remaining)) {
|
||||
qDebug() << "[DNS Tunnel DoT] Timeout waiting for response length";
|
||||
socket.close();
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray lengthBytes = socket.read(2);
|
||||
if (lengthBytes.size() != 2) {
|
||||
qDebug() << "[DNS Tunnel DoT] Failed to read response length";
|
||||
socket.close();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
quint16 responseLength = qFromBigEndian<quint16>(*reinterpret_cast<const quint16*>(lengthBytes.constData()));
|
||||
|
||||
// Now read the full response body
|
||||
QByteArray response;
|
||||
while (response.size() < responseLength) {
|
||||
int remaining = timeoutMsecs - timer.elapsed();
|
||||
if (remaining <= 0) {
|
||||
qDebug() << "[DNS Tunnel DoT] Timeout waiting for response body";
|
||||
socket.close();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (socket.bytesAvailable() > 0) {
|
||||
response.append(socket.read(responseLength - response.size()));
|
||||
} else if (!socket.waitForReadyRead(remaining)) {
|
||||
qDebug() << "[DNS Tunnel DoT] Timeout waiting for more data";
|
||||
socket.close();
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
socket.close();
|
||||
|
||||
qDebug() << "[DNS Tunnel DoT] Received response:" << response.size() << "bytes";
|
||||
return parseDnsTxtResponse(response);
|
||||
}
|
||||
@@ -1649,7 +1684,9 @@ QByteArray NetworkUtilities::sendViaDnsTunnelHttps(const QByteArray &payload, co
|
||||
}
|
||||
|
||||
// DoH uses HTTP POST with application/dns-message
|
||||
QString url = QString("http://%1:%2%3").arg(dnsServer).arg(port).arg(endpoint);
|
||||
// Use HTTPS for port 443, HTTP for other ports (like 80 for local testing)
|
||||
QString scheme = (port == 443) ? "https" : "http";
|
||||
QString url = QString("%1://%2:%3%4").arg(scheme).arg(dnsServer).arg(port).arg(endpoint);
|
||||
|
||||
qDebug() << "[DNS Tunnel DoH] Sending to" << url;
|
||||
|
||||
@@ -1702,15 +1739,24 @@ QByteArray NetworkUtilities::sendViaDnsTunnelUdpChunked(const QByteArray &payloa
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Helper lambda to send UDP request and get raw response
|
||||
auto sendUdpRequest = [&](const QByteArray &query) -> QByteArray {
|
||||
// Constants for retry logic
|
||||
constexpr int MAX_INITIAL_RETRIES = 3;
|
||||
constexpr int MAX_CHUNK_RETRIES = 2;
|
||||
constexpr int MAX_CONCURRENT_REQUESTS = 5;
|
||||
constexpr int BASE_TIMEOUT_MS = 2000;
|
||||
|
||||
// Helper lambda to send UDP request with configurable timeout
|
||||
auto sendUdpRequestWithTimeout = [&](const QByteArray &query, int requestTimeoutMs) -> QByteArray {
|
||||
QUdpSocket socket;
|
||||
socket.writeDatagram(query, dnsAddress, port);
|
||||
qint64 written = socket.writeDatagram(query, dnsAddress, port);
|
||||
if (written != query.size()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
timer.setInterval(timeoutMsecs / 5); // Shorter timeout per request
|
||||
timer.setInterval(requestTimeoutMs);
|
||||
|
||||
QByteArray response;
|
||||
bool responseReceived = false;
|
||||
@@ -1734,7 +1780,27 @@ QByteArray NetworkUtilities::sendViaDnsTunnelUdpChunked(const QByteArray &payloa
|
||||
return responseReceived ? response : QByteArray();
|
||||
};
|
||||
|
||||
// Send initial request with payload
|
||||
// Helper lambda with retry and exponential backoff
|
||||
auto sendWithRetry = [&](const QByteArray &query, int maxRetries) -> QByteArray {
|
||||
for (int attempt = 0; attempt < maxRetries; attempt++) {
|
||||
int timeout = BASE_TIMEOUT_MS * (attempt + 1); // 2s, 4s, 6s
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Attempt" << (attempt + 1) << "/" << maxRetries
|
||||
<< "timeout:" << timeout << "ms";
|
||||
|
||||
QByteArray response = sendUdpRequestWithTimeout(query, timeout);
|
||||
if (!response.isEmpty()) {
|
||||
return response;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries - 1) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Retry after" << (timeout / 2) << "ms";
|
||||
QThread::msleep(timeout / 2);
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
};
|
||||
|
||||
// === Step 1: Send initial request with retry ===
|
||||
quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
|
||||
QByteArray initialQuery = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
|
||||
|
||||
@@ -1744,10 +1810,10 @@ QByteArray NetworkUtilities::sendViaDnsTunnelUdpChunked(const QByteArray &payloa
|
||||
}
|
||||
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Sending initial request, payload:" << payload.size() << "bytes";
|
||||
QByteArray firstResponse = sendUdpRequest(initialQuery);
|
||||
QByteArray firstResponse = sendWithRetry(initialQuery, MAX_INITIAL_RETRIES);
|
||||
|
||||
if (firstResponse.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] No response for initial request";
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] No response for initial request after" << MAX_INITIAL_RETRIES << "attempts";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@@ -1762,7 +1828,6 @@ QByteArray NetworkUtilities::sendViaDnsTunnelUdpChunked(const QByteArray &payloa
|
||||
|
||||
// Check if response is chunked
|
||||
if (meta.totalChunks <= 1) {
|
||||
// Not chunked or single chunk - return as-is
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Single chunk response:" << firstTxtData.size() << "bytes";
|
||||
return firstTxtData;
|
||||
}
|
||||
@@ -1770,51 +1835,137 @@ QByteArray NetworkUtilities::sendViaDnsTunnelUdpChunked(const QByteArray &payloa
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Chunked response: total=" << meta.totalChunks
|
||||
<< "size=" << meta.totalSize << "chunkId=" << meta.chunkId.toHex();
|
||||
|
||||
// Collect all chunks
|
||||
// === Step 2: Collect all chunks ===
|
||||
QMap<int, QByteArray> chunks;
|
||||
chunks[0] = firstTxtData;
|
||||
|
||||
// Request remaining chunks
|
||||
// Build list of chunks to request
|
||||
QList<int> chunksToRequest;
|
||||
for (int i = 1; i < meta.totalChunks; i++) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Requesting chunk" << i;
|
||||
chunksToRequest.append(i);
|
||||
}
|
||||
|
||||
// === Step 3: Request chunks in parallel batches with retry ===
|
||||
auto requestChunksBatch = [&](const QList<int> &chunkIndices, int batchTimeout) {
|
||||
if (chunkIndices.isEmpty()) return;
|
||||
|
||||
quint16 chunkTxId = static_cast<quint16>((QDateTime::currentMSecsSinceEpoch() + i) & 0xFFFF);
|
||||
QByteArray chunkQuery = buildDnsChunkRequest(queryName, chunkTxId, meta.chunkId, i);
|
||||
// Create sockets and send requests for this batch
|
||||
QList<QSharedPointer<QUdpSocket>> sockets;
|
||||
QMap<QUdpSocket*, int> socketToIndex;
|
||||
|
||||
if (chunkQuery.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Failed to build chunk request" << i;
|
||||
continue;
|
||||
for (int idx : chunkIndices) {
|
||||
if (chunks.contains(idx)) continue; // Already have this chunk
|
||||
|
||||
quint16 chunkTxId = static_cast<quint16>((QDateTime::currentMSecsSinceEpoch() + idx) & 0xFFFF);
|
||||
QByteArray chunkQuery = buildDnsChunkRequest(queryName, chunkTxId, meta.chunkId, idx);
|
||||
|
||||
if (chunkQuery.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Failed to build chunk request" << idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto socket = QSharedPointer<QUdpSocket>::create();
|
||||
socket->writeDatagram(chunkQuery, dnsAddress, port);
|
||||
socketToIndex[socket.data()] = idx;
|
||||
sockets.append(socket);
|
||||
}
|
||||
|
||||
QByteArray chunkResponse = sendUdpRequest(chunkQuery);
|
||||
if (chunkResponse.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] No response for chunk" << i;
|
||||
continue;
|
||||
if (sockets.isEmpty()) return;
|
||||
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Sent" << sockets.size() << "parallel requests";
|
||||
|
||||
// Wait for responses with deadline
|
||||
QEventLoop loop;
|
||||
QTimer deadline;
|
||||
deadline.setSingleShot(true);
|
||||
deadline.setInterval(batchTimeout);
|
||||
|
||||
int receivedCount = 0;
|
||||
int expectedCount = sockets.size();
|
||||
|
||||
QObject::connect(&deadline, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
|
||||
for (auto &socket : sockets) {
|
||||
QObject::connect(socket.data(), &QUdpSocket::readyRead, [&, sock = socket.data()]() {
|
||||
while (sock->hasPendingDatagrams()) {
|
||||
QNetworkDatagram datagram = sock->receiveDatagram();
|
||||
if (datagram.isValid()) {
|
||||
QByteArray chunkTxtData = parseDnsTxtResponse(datagram.data());
|
||||
if (!chunkTxtData.isEmpty()) {
|
||||
ChunkMeta chunkMeta = parseChunkMeta(datagram.data());
|
||||
int idx = (chunkMeta.totalChunks > 0) ? chunkMeta.chunkIndex : socketToIndex.value(sock, -1);
|
||||
if (idx >= 0 && !chunks.contains(idx)) {
|
||||
chunks[idx] = chunkTxtData;
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Received chunk" << idx << ":" << chunkTxtData.size() << "bytes";
|
||||
receivedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit early if we have all chunks
|
||||
if (receivedCount >= expectedCount || chunks.size() >= meta.totalChunks) {
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray chunkTxtData = parseDnsTxtResponse(chunkResponse);
|
||||
if (!chunkTxtData.isEmpty()) {
|
||||
ChunkMeta chunkMeta = parseChunkMeta(chunkResponse);
|
||||
int idx = (chunkMeta.totalChunks > 0) ? chunkMeta.chunkIndex : i;
|
||||
chunks[idx] = chunkTxtData;
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Received chunk" << idx << ":" << chunkTxtData.size() << "bytes";
|
||||
deadline.start();
|
||||
loop.exec();
|
||||
deadline.stop();
|
||||
};
|
||||
|
||||
// Process chunks in batches
|
||||
int totalTimeout = qMax(timeoutMsecs / 2, 5000); // At least 5 seconds for chunks
|
||||
int batchTimeout = totalTimeout / (MAX_CHUNK_RETRIES + 1);
|
||||
|
||||
for (int retryRound = 0; retryRound <= MAX_CHUNK_RETRIES; retryRound++) {
|
||||
// Find missing chunks
|
||||
QList<int> missing;
|
||||
for (int i = 1; i < meta.totalChunks; i++) {
|
||||
if (!chunks.contains(i)) {
|
||||
missing.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] All chunks received";
|
||||
break;
|
||||
}
|
||||
|
||||
if (retryRound > 0) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Retry round" << retryRound << "for" << missing.size() << "missing chunks";
|
||||
}
|
||||
|
||||
// Process in batches of MAX_CONCURRENT_REQUESTS
|
||||
for (int batchStart = 0; batchStart < missing.size(); batchStart += MAX_CONCURRENT_REQUESTS) {
|
||||
QList<int> batch = missing.mid(batchStart, MAX_CONCURRENT_REQUESTS);
|
||||
requestChunksBatch(batch, batchTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have all chunks
|
||||
if (chunks.size() != meta.totalChunks) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Missing chunks:" << chunks.size() << "/" << meta.totalChunks;
|
||||
// Try to return what we have anyway
|
||||
}
|
||||
|
||||
// Combine all chunks in order
|
||||
QByteArray combined;
|
||||
// === Step 4: Verify all chunks received ===
|
||||
QList<int> finalMissing;
|
||||
for (int i = 0; i < meta.totalChunks; i++) {
|
||||
if (chunks.contains(i)) {
|
||||
combined.append(chunks[i]);
|
||||
if (!chunks.contains(i)) {
|
||||
finalMissing.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Combined" << chunks.size() << "chunks," << combined.size() << "bytes";
|
||||
if (!finalMissing.isEmpty()) {
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] FAILED: Missing chunks after all retries:" << finalMissing;
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] Received" << chunks.size() << "/" << meta.totalChunks << "chunks";
|
||||
return QByteArray(); // Return empty - don't return partial/corrupted data
|
||||
}
|
||||
|
||||
// === Step 5: Combine all chunks in order ===
|
||||
QByteArray combined;
|
||||
combined.reserve(meta.totalSize > 0 ? meta.totalSize : meta.totalChunks * 500);
|
||||
|
||||
for (int i = 0; i < meta.totalChunks; i++) {
|
||||
combined.append(chunks[i]);
|
||||
}
|
||||
|
||||
qDebug() << "[DNS Tunnel UDP Chunked] SUCCESS: Combined" << meta.totalChunks << "chunks," << combined.size() << "bytes";
|
||||
return combined;
|
||||
}
|
||||
|
||||
44
client/gateway.example.json
Normal file
44
client/gateway.example.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"primary": "http",
|
||||
"retry_count": 3,
|
||||
"timeout_ms": 10000,
|
||||
|
||||
"http": {
|
||||
"enabled": true,
|
||||
"endpoint": "https://your-gateway.example.com/"
|
||||
},
|
||||
|
||||
"dns_transports": [
|
||||
{
|
||||
"type": "udp",
|
||||
"server": "your-gateway.example.com",
|
||||
"domain": "gateway.example.com",
|
||||
"port": 5453
|
||||
},
|
||||
{
|
||||
"type": "tcp",
|
||||
"server": "your-gateway.example.com",
|
||||
"domain": "gateway.example.com",
|
||||
"port": 5453
|
||||
},
|
||||
{
|
||||
"type": "dot",
|
||||
"server": "your-gateway.example.com",
|
||||
"domain": "gateway.example.com",
|
||||
"port": 8853
|
||||
},
|
||||
{
|
||||
"type": "doh",
|
||||
"server": "your-gateway.example.com",
|
||||
"domain": "gateway.example.com",
|
||||
"port": 443,
|
||||
"path": "/dns-query"
|
||||
},
|
||||
{
|
||||
"type": "doq",
|
||||
"server": "your-gateway.example.com",
|
||||
"domain": "gateway.example.com",
|
||||
"port": 8854
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,7 +14,8 @@ namespace
|
||||
const char cloudFlareNs1[] = "1.1.1.1";
|
||||
const char cloudFlareNs2[] = "1.0.0.1";
|
||||
|
||||
constexpr char gatewayEndpoint[] = "http://localhost:80/";
|
||||
//constexpr char gatewayEndpoint[] = "http://localhost:80/";
|
||||
constexpr char gatewayEndpoint[] = "http://127.0.0.1:80/";
|
||||
}
|
||||
|
||||
Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this)
|
||||
|
||||
@@ -724,9 +724,6 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||
|
||||
@@ -742,32 +739,18 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
||||
apiPayload[configKey::apiEndpoint] = serverConfig.value(configKey::apiEndpoint).toString();
|
||||
|
||||
QByteArray responseBody;
|
||||
QString baseDomain = "gateway.example.com";
|
||||
QString endpoint = QString("%1v1/proxy_config");
|
||||
|
||||
// Try DNS transports first
|
||||
// 1. UDP
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Udp, 15353);
|
||||
ErrorCode errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
// 2. TCP
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tcp, 15353);
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
// 3. DoT
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tls, 8853);
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
// 4. DoH
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Https, 80, "/dns-query");
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
// 5. Fallback to HTTP
|
||||
errorCode = gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
// Use GatewayController with parallel transports
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(),
|
||||
m_settings->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
// Load transports config from file or env
|
||||
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
|
||||
|
||||
ErrorCode errorCode = gatewayController.postParallel(endpoint, apiPayload, responseBody);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = fillServerConfig(serviceProtocol, protocolData, responseBody, serverConfig);
|
||||
@@ -974,42 +957,14 @@ ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &respo
|
||||
ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody,
|
||||
bool isTestPurchase)
|
||||
{
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase),
|
||||
m_settings->isDevGatewayEnv(isTestPurchase),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
// 1. HTTP (primary transport)
|
||||
ErrorCode result = gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
if (result == ErrorCode::NoError) return result;
|
||||
// Load transports config from file or env
|
||||
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
|
||||
|
||||
qDebug() << "[Transport] HTTP failed, trying DNS transports as fallback";
|
||||
|
||||
// DNS tunneling fallback - base_domain: gateway.example.com
|
||||
// Порты бэкенда: UDP/TCP=15353, DoT=8853, DoQ=8854
|
||||
const QString baseDomain = "gateway.example.com";
|
||||
|
||||
// 2. DNS UDP (порт 15353)
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Udp, 15353);
|
||||
result = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (result == ErrorCode::NoError) return result;
|
||||
|
||||
// 3. DNS TCP (порт 15353)
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tcp, 15353);
|
||||
result = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (result == ErrorCode::NoError) return result;
|
||||
|
||||
// 4. DoT (порт 8853)
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tls, 8853);
|
||||
result = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (result == ErrorCode::NoError) return result;
|
||||
|
||||
// 5. DoH (порт 80, endpoint /dns-query)
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Https, 80, "/dns-query");
|
||||
result = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (result == ErrorCode::NoError) return result;
|
||||
|
||||
// 6. DoQ (порт 8854)
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Quic, 8854);
|
||||
result = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
|
||||
return result;
|
||||
// Parallel request via all configured transports (HTTP + DNS)
|
||||
return gatewayController.postParallel(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "apiNewsController.h"
|
||||
|
||||
#include "core/api/apiUtils.h"
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -32,8 +33,6 @@ void ApiNewsController::fetchNews(bool showError)
|
||||
return;
|
||||
}
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||
|
||||
@@ -45,74 +44,36 @@ void ApiNewsController::fetchNews(bool showError)
|
||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
||||
}
|
||||
|
||||
QString baseDomain = "gateway.example.com";
|
||||
QString endpoint = QString("%1v1/news");
|
||||
|
||||
// 1. HTTP (primary transport - async)
|
||||
auto future = gatewayController->postAsync(endpoint, payload);
|
||||
future.then(this, [this, showError, gatewayController, baseDomain, endpoint, payload](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [errorCode, responseBody] = result;
|
||||
|
||||
// HTTP succeeded
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||
QJsonArray newsArray;
|
||||
if (doc.isArray()) {
|
||||
newsArray = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.value("news").isArray()) {
|
||||
newsArray = obj.value("news").toArray();
|
||||
}
|
||||
}
|
||||
m_newsModel->updateModel(newsArray);
|
||||
emit fetchNewsFinished();
|
||||
return;
|
||||
// Use GatewayController with parallel transports
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(),
|
||||
m_settings->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
// Load transports config from file or env
|
||||
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = gatewayController.postParallel(endpoint, payload, responseBody);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit errorOccurred(errorCode, showError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
||||
QJsonArray newsArray;
|
||||
if (doc.isArray()) {
|
||||
newsArray = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.value("news").isArray()) {
|
||||
newsArray = obj.value("news").toArray();
|
||||
}
|
||||
|
||||
// HTTP failed, try DNS transports as fallback (synchronous)
|
||||
qDebug() << "[Transport] HTTP failed, trying DNS transports as fallback";
|
||||
GatewayController dnsGateway(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
QByteArray dnsResponseBody;
|
||||
ErrorCode dnsError = ErrorCode::UnknownError;
|
||||
|
||||
// 2. DNS UDP
|
||||
dnsGateway.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Udp, 15353);
|
||||
dnsError = dnsGateway.postViaDns(endpoint, payload, dnsResponseBody);
|
||||
if (dnsError != ErrorCode::NoError) {
|
||||
// 3. DNS TCP
|
||||
dnsGateway.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tcp, 15353);
|
||||
dnsError = dnsGateway.postViaDns(endpoint, payload, dnsResponseBody);
|
||||
}
|
||||
if (dnsError != ErrorCode::NoError) {
|
||||
// 4. DoT
|
||||
dnsGateway.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tls, 8853);
|
||||
dnsError = dnsGateway.postViaDns(endpoint, payload, dnsResponseBody);
|
||||
}
|
||||
if (dnsError != ErrorCode::NoError) {
|
||||
// 5. DoH
|
||||
dnsGateway.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Https, 80, "/dns-query");
|
||||
dnsError = dnsGateway.postViaDns(endpoint, payload, dnsResponseBody);
|
||||
}
|
||||
|
||||
if (dnsError != ErrorCode::NoError) {
|
||||
emit errorOccurred(dnsError, showError);
|
||||
return;
|
||||
}
|
||||
|
||||
// DNS succeeded
|
||||
QJsonDocument doc = QJsonDocument::fromJson(dnsResponseBody);
|
||||
QJsonArray newsArray;
|
||||
if (doc.isArray()) {
|
||||
newsArray = doc.array();
|
||||
} else if (doc.isObject()) {
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.value("news").isArray()) {
|
||||
newsArray = obj.value("news").toArray();
|
||||
}
|
||||
}
|
||||
m_newsModel->updateModel(newsArray);
|
||||
emit fetchNewsFinished();
|
||||
});
|
||||
}
|
||||
m_newsModel->updateModel(newsArray);
|
||||
emit fetchNewsFinished();
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
auto authData = serverConfig.value(configKey::authData).toObject();
|
||||
|
||||
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
||||
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
||||
@@ -71,35 +69,19 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||
|
||||
QByteArray responseBody;
|
||||
QString baseDomain = "gateway.example.com";
|
||||
QString endpoint = QString("%1v1/account_info");
|
||||
|
||||
// 1. HTTP (primary transport)
|
||||
ErrorCode errorCode = gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
if (errorCode == ErrorCode::NoError) goto success;
|
||||
// Use GatewayController with parallel transports
|
||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase),
|
||||
m_settings->isDevGatewayEnv(isTestPurchase),
|
||||
requestTimeoutMsecs,
|
||||
m_settings->isStrictKillSwitchEnabled());
|
||||
|
||||
qDebug() << "[Transport] HTTP failed, trying DNS transports as fallback";
|
||||
// Load transports config from file or env
|
||||
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
|
||||
|
||||
// 2. DNS UDP
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Udp, 15353);
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (errorCode == ErrorCode::NoError) goto success;
|
||||
ErrorCode errorCode = gatewayController.postParallel(endpoint, apiPayload, responseBody);
|
||||
|
||||
// 3. DNS TCP
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tcp, 15353);
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (errorCode == ErrorCode::NoError) goto success;
|
||||
|
||||
// 4. DoT
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Tls, 8853);
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
if (errorCode == ErrorCode::NoError) goto success;
|
||||
|
||||
// 5. DoH
|
||||
gatewayController.setDnsServer("127.0.0.1", baseDomain, NetworkUtilities::DnsTransport::Https, 80, "/dns-query");
|
||||
errorCode = gatewayController.postViaDns(endpoint, apiPayload, responseBody);
|
||||
|
||||
success:
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
emit errorOccurred(errorCode);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user