This commit is contained in:
svamnezia
2026-04-17 13:56:06 +03:00
parent 10289fdb26
commit eb1188ccd7
8 changed files with 121 additions and 117 deletions

View File

@@ -33,6 +33,17 @@ add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
add_definitions(-DAGW_DNS_SERVER="$ENV{AGW_DNS_SERVER}")
add_definitions(-DAGW_DNS_DOMAIN="$ENV{AGW_DNS_DOMAIN}")
add_definitions(-DAGW_DNS_PRIMARY="$ENV{AGW_DNS_PRIMARY}")
add_definitions(-DAGW_DNS_PORT_UDP="$ENV{AGW_DNS_PORT_UDP}")
add_definitions(-DAGW_DNS_PORT_DOT="$ENV{AGW_DNS_PORT_DOT}")
add_definitions(-DAGW_DNS_PORT_DOH="$ENV{AGW_DNS_PORT_DOH}")
add_definitions(-DAGW_DNS_PORT_DOQ="$ENV{AGW_DNS_PORT_DOQ}")
add_definitions(-DAGW_DNS_DOH_PATH="$ENV{AGW_DNS_DOH_PATH}")
add_definitions(-DAGW_DNS_RETRY_COUNT="$ENV{AGW_DNS_RETRY_COUNT}")
add_definitions(-DAGW_DNS_TIMEOUT_MS="$ENV{AGW_DNS_TIMEOUT_MS}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()
@@ -227,17 +238,6 @@ 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})

View File

@@ -6,13 +6,11 @@
#include <random>
#include <QCryptographicHash>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMutex>
#include <QNetworkReply>
#include <QProcessEnvironment>
#include <QPromise>
#include <QUrl>
#include <QHostAddress>
@@ -194,39 +192,67 @@ void GatewayController::setTransportsConfig(const TransportsConfig &config)
<< "timeout=" << config.timeoutMs;
}
bool GatewayController::loadTransportsConfig(const QString &filePath, const QString &envVarName)
TransportsConfig GatewayController::buildTransportsConfig()
{
// 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();
TransportsConfig config;
QString server(AGW_DNS_SERVER);
QString domain(AGW_DNS_DOMAIN);
if (server.isEmpty() || domain.isEmpty()) {
qDebug() << "[Transport] DNS server/domain not configured, HTTP only";
return config;
}
// 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();
QString primaryStr = QString(AGW_DNS_PRIMARY).toLower();
if (primaryStr == "udp" || primaryStr == "dns_udp") {
config.primary = PrimaryTransport::DnsUdp;
} else if (primaryStr == "tcp" || primaryStr == "dns_tcp") {
config.primary = PrimaryTransport::DnsTcp;
} else if (primaryStr == "dot" || primaryStr == "dns_dot") {
config.primary = PrimaryTransport::DnsDot;
} else if (primaryStr == "doh" || primaryStr == "dns_doh") {
config.primary = PrimaryTransport::DnsDoh;
} else if (primaryStr == "doq" || primaryStr == "dns_doq") {
config.primary = PrimaryTransport::DnsDoq;
} else {
config.primary = PrimaryTransport::Http;
}
qDebug() << "[Transport] No config found, using defaults";
return false;
int retryCount = QString(AGW_DNS_RETRY_COUNT).toInt();
config.retryCount = (retryCount > 0) ? retryCount : 3;
int timeoutMs = QString(AGW_DNS_TIMEOUT_MS).toInt();
config.timeoutMs = (timeoutMs > 0) ? timeoutMs : 10000;
config.httpEnabled = true;
auto addTransport = [&](NetworkUtilities::DnsTransport type, const char *portDefine, quint16 defaultPort,
const QString &dohPath = QString()) {
DnsTransportEntry entry;
entry.type = type;
entry.server = server;
entry.domain = domain;
quint16 port = QString(portDefine).toUShort();
entry.port = (port > 0) ? port : defaultPort;
if (!dohPath.isEmpty()) entry.dohPath = dohPath;
config.dnsTransports.append(entry);
};
addTransport(NetworkUtilities::DnsTransport::Udp, AGW_DNS_PORT_UDP, 5353);
addTransport(NetworkUtilities::DnsTransport::Tcp, AGW_DNS_PORT_UDP, 5353);
addTransport(NetworkUtilities::DnsTransport::Tls, AGW_DNS_PORT_DOT, 853);
QString dohPath = QString(AGW_DNS_DOH_PATH);
if (dohPath.isEmpty()) dohPath = "/dns-query";
addTransport(NetworkUtilities::DnsTransport::Https, AGW_DNS_PORT_DOH, 443, dohPath);
addTransport(NetworkUtilities::DnsTransport::Quic, AGW_DNS_PORT_DOQ, 8853);
qDebug() << "[Transport] Built config from env: server=" << server << "domain=" << domain
<< "transports=" << config.dnsTransports.size() << "primary=" << static_cast<int>(config.primary);
return config;
}
ErrorCode GatewayController::postParallel(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)

View File

@@ -62,8 +62,7 @@ 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");
static TransportsConfig buildTransportsConfig();
void setTransportsConfig(const TransportsConfig &config);
// NEW: Parallel request via all configured transports (primary first, then others)

View File

@@ -30,6 +30,17 @@ endif()
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DAGW_DNS_SERVER="$ENV{AGW_DNS_SERVER}")
add_definitions(-DAGW_DNS_DOMAIN="$ENV{AGW_DNS_DOMAIN}")
add_definitions(-DAGW_DNS_PRIMARY="$ENV{AGW_DNS_PRIMARY}")
add_definitions(-DAGW_DNS_PORT_UDP="$ENV{AGW_DNS_PORT_UDP}")
add_definitions(-DAGW_DNS_PORT_DOT="$ENV{AGW_DNS_PORT_DOT}")
add_definitions(-DAGW_DNS_PORT_DOH="$ENV{AGW_DNS_PORT_DOH}")
add_definitions(-DAGW_DNS_PORT_DOQ="$ENV{AGW_DNS_PORT_DOQ}")
add_definitions(-DAGW_DNS_DOH_PATH="$ENV{AGW_DNS_DOH_PATH}")
add_definitions(-DAGW_DNS_RETRY_COUNT="$ENV{AGW_DNS_RETRY_COUNT}")
add_definitions(-DAGW_DNS_TIMEOUT_MS="$ENV{AGW_DNS_TIMEOUT_MS}")
qt_add_executable(${PROJECT_NAME}
tst_transports.cpp
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
@@ -65,14 +76,4 @@ if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 crypt32)
endif()
# Copy gateway.json next to the test binary
if(EXISTS "${CLIENT_ROOT_DIR}/gateway.json")
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CLIENT_ROOT_DIR}/gateway.json"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>"
)
endif()
add_test(NAME TransportTest COMMAND ${PROJECT_NAME})

View File

@@ -2,12 +2,8 @@
#include <QDebug>
#include <QElapsedTimer>
#include <QEventLoop>
#include <QFile>
#include <QHostAddress>
#include <QHostInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
@@ -46,52 +42,44 @@ struct TestConfig {
int timeoutMs = 15000;
};
static TestConfig loadConfig(const QString &path)
static TestConfig buildConfigFromEnv()
{
TestConfig cfg;
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << "Cannot open config:" << path;
return cfg;
}
QJsonObject json = QJsonDocument::fromJson(f.readAll()).object();
if (json.contains("http")) {
cfg.httpEndpoint = json["http"].toObject().value("endpoint").toString();
}
cfg.timeoutMs = json.value("timeout_ms").toInt(15000);
QString server(AGW_DNS_SERVER);
QString domain(AGW_DNS_DOMAIN);
if (json.contains("dns_transports")) {
for (const auto &v : json["dns_transports"].toArray()) {
QJsonObject obj = v.toObject();
TestConfig::DnsEntry e;
e.server = obj.value("server").toString();
e.domain = obj.value("domain").toString();
e.port = static_cast<quint16>(obj.value("port").toInt(5353));
e.dohPath = obj.value("path").toString("/dns-query");
cfg.httpEndpoint = QString(DEV_AGW_PUBLIC_KEY).isEmpty()
? QString() : QString("http://%1/").arg(server);
int timeout = QString(AGW_DNS_TIMEOUT_MS).toInt();
cfg.timeoutMs = (timeout > 0) ? timeout : 15000;
if (server.isEmpty() || domain.isEmpty()) return cfg;
auto addEntry = [&](NetworkUtilities::DnsTransport type, const QString &name,
const char *portDefine, quint16 defaultPort, const QString &dohPath = QString()) {
TestConfig::DnsEntry e;
e.type = type;
e.name = name;
e.server = server;
e.domain = domain;
quint16 port = QString(portDefine).toUShort();
e.port = (port > 0) ? port : defaultPort;
if (!dohPath.isEmpty()) e.dohPath = dohPath;
cfg.dnsTransports.append(e);
};
addEntry(NetworkUtilities::DnsTransport::Udp, "UDP", AGW_DNS_PORT_UDP, 5353);
addEntry(NetworkUtilities::DnsTransport::Tcp, "TCP", AGW_DNS_PORT_UDP, 5353);
addEntry(NetworkUtilities::DnsTransport::Tls, "DoT", AGW_DNS_PORT_DOT, 853);
QString dohPath = QString(AGW_DNS_DOH_PATH);
if (dohPath.isEmpty()) dohPath = "/dns-query";
addEntry(NetworkUtilities::DnsTransport::Https, "DoH", AGW_DNS_PORT_DOH, 443, dohPath);
addEntry(NetworkUtilities::DnsTransport::Quic, "DoQ", AGW_DNS_PORT_DOQ, 8853);
QString t = obj.value("type").toString().toLower();
if (t == "udp") {
e.type = NetworkUtilities::DnsTransport::Udp;
e.name = "UDP";
} else if (t == "tcp") {
e.type = NetworkUtilities::DnsTransport::Tcp;
e.name = "TCP";
} else if (t == "dot" || t == "tls") {
e.type = NetworkUtilities::DnsTransport::Tls;
e.name = "DoT";
} else if (t == "doh" || t == "https") {
e.type = NetworkUtilities::DnsTransport::Https;
e.name = "DoH";
} else if (t == "doq" || t == "quic") {
e.type = NetworkUtilities::DnsTransport::Quic;
e.name = "DoQ";
} else {
continue;
}
cfg.dnsTransports.append(e);
}
}
return cfg;
}
@@ -251,15 +239,10 @@ private:
private slots:
void initTestCase()
{
QString configPath = QCoreApplication::applicationDirPath() + "/gateway.json";
if (!QFile::exists(configPath)) {
configPath = QString(CLIENT_SOURCE_DIR) + "/gateway.json";
}
qDebug() << "Loading config from:" << configPath;
m_config = loadConfig(configPath);
m_config = buildConfigFromEnv();
QVERIFY2(!m_config.httpEndpoint.isEmpty(), "gateway.json: http endpoint missing");
QVERIFY2(!m_config.dnsTransports.isEmpty(), "gateway.json: no dns_transports configured");
QVERIFY2(!m_config.dnsTransports.isEmpty(),
"AGW_DNS_SERVER / AGW_DNS_DOMAIN not set — cannot run transport tests");
qDebug() << "HTTP endpoint:" << m_config.httpEndpoint;
qDebug() << "DNS transports:" << m_config.dnsTransports.size();

View File

@@ -747,8 +747,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
// Load transports config from file or env
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
gatewayController.setTransportsConfig(GatewayController::buildTransportsConfig());
ErrorCode errorCode = gatewayController.postParallel(endpoint, apiPayload, responseBody);
@@ -962,9 +961,7 @@ ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJ
apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
// Load transports config from file or env
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
gatewayController.setTransportsConfig(GatewayController::buildTransportsConfig());
// Parallel request via all configured transports (HTTP + DNS)
return gatewayController.postParallel(endpoint, apiPayload, responseBody);
}

View File

@@ -52,8 +52,7 @@ void ApiNewsController::fetchNews(bool showError)
apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
// Load transports config from file or env
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
gatewayController.setTransportsConfig(GatewayController::buildTransportsConfig());
QByteArray responseBody;
ErrorCode errorCode = gatewayController.postParallel(endpoint, payload, responseBody);

View File

@@ -77,8 +77,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
// Load transports config from file or env
gatewayController.loadTransportsConfig("gateway.json", "AMNEZIA_GATEWAY");
gatewayController.setTransportsConfig(GatewayController::buildTransportsConfig());
ErrorCode errorCode = gatewayController.postParallel(endpoint, apiPayload, responseBody);