fix: prepare gateway request (#3291)

* fix: prepare the gateway request once instead of a blocking callback per attempt

* chore: libagw 1.0.8, gateway and failover timeouts 6s, health check 1s
This commit is contained in:
vkamn authored and GitHub committed 2026-10-16 00:10:29 +08:00
1 parent db951e9928
commit 30ff3451c7
6 files changed
+63 -36

No files matched your search

+50 -29
View File
@@ -5,11 +5,13 @@
#include <QFutureWatcher>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMetaObject>
#include <QJsonObject>
#include <QThread>
#include <QUrl>
#include <QtConcurrent/QtConcurrentRun>
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/api/apiUtils.h"
@@ -32,6 +34,9 @@ namespace
constexpr QLatin1String s3FallbackEndpoints("s3_fallback_endpoints");
constexpr QLatin1String isDevEnvironment("is_dev_environment");
constexpr QLatin1String requestTimeoutMsecs("request_timeout_msecs");
constexpr QLatin1String proxyRequestTimeoutMsecs("proxy_request_timeout_msecs");
constexpr QLatin1String proxyStorageTimeoutMsecs("proxy_storage_timeout_msecs");
constexpr QLatin1String proxyHealthTimeoutMsecs("proxy_health_timeout_msecs");
}
namespace agwOptionsKey
@@ -40,6 +45,11 @@ namespace
constexpr QLatin1String userCountryCode("user_country_code");
}
namespace agwStateKey
{
constexpr QLatin1String workingProxy("working_proxy");
}
constexpr QLatin1String agwStateCacheKey("agw_state");
constexpr QLatin1String agwTracePrefix("agw trace:");
constexpr QLatin1String agwTraceFailoverMarker("suspicious");
@@ -71,6 +81,7 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
QObject *parent)
: QObject(parent),
m_gatewayEndpoint(gatewayEndpoint),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
m_appSettingsRepository(appSettingsRepository)
{
@@ -93,13 +104,11 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
config[agwConfigKey::s3FallbackEndpoints] = QJsonArray::fromStringList(fallbackEndpoints);
config[agwConfigKey::isDevEnvironment] = isDevEnvironment;
config[agwConfigKey::requestTimeoutMsecs] = requestTimeoutMsecs;
config[agwConfigKey::proxyRequestTimeoutMsecs] = apiDefs::proxyRequestTimeoutMsecs;
config[agwConfigKey::proxyStorageTimeoutMsecs] = apiDefs::proxyStorageTimeoutMsecs;
config[agwConfigKey::proxyHealthTimeoutMsecs] = apiDefs::proxyHealthTimeoutMsecs;
agw_callbacks callbacks {};
callbacks.struct_size = sizeof(agw_callbacks);
callbacks.on_before_request = &GatewayController::onBeforeRequest;
callbacks.on_before_request_user_data = this;
m_client = agw_client_create(QJsonDocument(config).toJson(QJsonDocument::Compact).constData(), &callbacks);
m_client = agw_client_create(QJsonDocument(config).toJson(QJsonDocument::Compact).constData(), nullptr);
if (m_client == 0) {
qCritical() << "GatewayController: failed to create gateway client (missing key or endpoint?)";
return;
@@ -137,6 +146,7 @@ amnezia::ErrorCode GatewayController::post(const QString &endpoint, const QJsonO
QFuture<QPair<amnezia::ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
{
prepareRequest();
return QtConcurrent::run(
[this, endpoint, apiPayload]() -> QPair<amnezia::ErrorCode, QByteArray> { return executePost(endpoint, apiPayload); });
}
@@ -190,44 +200,55 @@ amnezia::ErrorCode GatewayController::mapResultCode(const int code, const QByteA
}
}
void GatewayController::onBeforeRequest(const char *host, void *userData)
{
auto *controller = static_cast<GatewayController *>(userData);
const QString hostString = QString::fromUtf8(host);
if (QThread::currentThread() == controller->thread()) {
controller->handleBeforeRequest(hostString);
} else {
QMetaObject::invokeMethod(
controller, [controller, hostString]() { controller->handleBeforeRequest(hostString); },
Qt::BlockingQueuedConnection);
}
}
void GatewayController::handleBeforeRequest(const QString &host)
void GatewayController::prepareRequest()
{
#ifdef Q_OS_IOS
Q_UNUSED(host);
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
if (!m_isStrictKillSwitchEnabled) {
return;
}
QStringList ips;
for (const QString &host : requestHosts()) {
const QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
ips.append(ip);
}
}
if (ips.isEmpty()) {
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(ips);
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "GatewayController::handleBeforeRequest(): failed to add killswitch exception for" << host;
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
}
});
}
}
#else
Q_UNUSED(host);
#endif
}
QStringList GatewayController::requestHosts() const
{
QStringList hosts;
if (m_client != 0) {
char *state = agw_export_state(m_client);
if (state != nullptr) {
const QString workingProxy = QJsonDocument::fromJson(QByteArray(state)).object().value(agwStateKey::workingProxy).toString();
agw_string_free(state);
if (!workingProxy.isEmpty()) {
hosts.append(QUrl(workingProxy).host());
}
}
}
hosts.append(QUrl(m_gatewayEndpoint).host());
hosts.removeAll(QString());
hosts.removeDuplicates();
return hosts;
}
void GatewayController::persistState()
{
if (m_client == 0 || m_appSettingsRepository == nullptr) {
+4 -2
View File
@@ -7,6 +7,7 @@
#include <QObject>
#include <QPair>
#include <QString>
#include <QStringList>
#include "core/utils/errorCodes.h"
@@ -28,13 +29,14 @@ public:
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private:
static void onBeforeRequest(const char *host, void *userData);
void handleBeforeRequest(const QString &host);
void prepareRequest();
QStringList requestHosts() const;
QPair<amnezia::ErrorCode, QByteArray> executePost(const QString &endpoint, const QJsonObject &apiPayload);
static amnezia::ErrorCode mapResultCode(const int code, const QByteArray &responseBody);
void persistState();
QString m_gatewayEndpoint;
bool m_isStrictKillSwitchEnabled = false;
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
+2 -1
View File
@@ -13,6 +13,7 @@
#include "core/controllers/gatewayController.h"
#include "core/utils/api/gatewayPayloadBuilder.h"
#include "core/utils/appUiConfig.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
@@ -140,7 +141,7 @@ void UpdateController::checkForUpdates()
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
7000,
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled(),
m_appSettingsRepository);
+4 -1
View File
@@ -4,7 +4,10 @@
namespace apiDefs
{
constexpr int requestTimeoutMsecs = 12 * 1000; // 12 secs
constexpr int requestTimeoutMsecs = 6 * 1000;
constexpr int proxyRequestTimeoutMsecs = 6 * 1000;
constexpr int proxyStorageTimeoutMsecs = 6 * 1000;
constexpr int proxyHealthTimeoutMsecs = 1000;
} // namespace apiDefs
+1 -1
View File
@@ -14,7 +14,7 @@ class AmneziaVPN(ConanFile):
def requirements(self):
os = str(self.settings.os)
self.requires("libagw/1.0.6")
self.requires("libagw/1.0.8")
has_ne = os == "iOS" or (os == "Macos" and self.options.macos_ne)
has_service = os == "Windows" or os == "Linux" or (os == "Macos" and not has_ne)
+2 -2
View File
@@ -13,7 +13,7 @@ import shlex
class Libagw(ConanFile):
name = "libagw"
version = "1.0.6"
version = "1.0.8"
settings = "os", "arch", "compiler"
_headers = ("agw.h", "agw_types.h")
@@ -91,7 +91,7 @@ class Libagw(ConanFile):
def source(self):
get(self, f"https://github.com/amnezia-vpn/libagw/archive/refs/tags/v{self.version}.zip",
sha256="148f421e20e31b53c7686a29a368e03720c41776facce457c55748d8b2974ddb", strip_root=True
sha256="ad36125be7ff4a77603107a0bc2194da6778d5612e649f53c9d954c742bb70db", strip_root=True
)
def generate(self):