Arquivos
tdesktop/Telegram/SourceFiles/core/proxy_rotation_manager.cpp
T
2026-04-16 21:24:39 +07:00

360 linhas
8.8 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/proxy_rotation_manager.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "mtproto/facade.h"
#include <algorithm>
namespace Core {
namespace {
constexpr auto kProxyRotationCheckInterval = 2 * crl::time(1000);
constexpr auto kProxyRotationCheckLifetime = 20 * crl::time(1000);
constexpr auto kProxyRotationMaxActiveChecks = 10;
} // namespace
ProxyRotationManager::ProxyRotationManager()
: _checkTimer([=] { runChecks(); })
, _switchTimer([=] { switchTimerDone(); }) {
App().domain().accountsChanges(
) | rpl::on_next([=] {
stopChecking();
reevaluate();
}, _lifetime);
}
void ProxyRotationManager::settingsChanged() {
stopChecking();
pruneRemovedEntries();
reevaluate();
}
void ProxyRotationManager::handleConnectionStateChanged(
not_null<Main::Account*> account,
int32 state) {
(void)account;
(void)state;
reevaluate();
}
bool ProxyRotationManager::shouldObserve() const {
const auto &settings = App().settings().proxy();
return settings.isEnabled()
&& settings.selected()
&& settings.proxyRotationEnabled()
&& (settings.list().size() > 1);
}
std::vector<not_null<Main::Account*>> ProxyRotationManager::productionAccounts() const {
auto result = std::vector<not_null<Main::Account*>>();
for (const auto &entry : App().domain().accounts()) {
const auto account = entry.account.get();
if (!account->sessionExists() || account->mtp().isTestMode()) {
continue;
}
result.push_back(account);
}
return result;
}
not_null<Main::Account*> ProxyRotationManager::accountForChecks() const {
if (App().someSessionExists()
&& App().activeAccount().sessionExists()
&& !App().activeAccount().mtp().isTestMode()) {
return &App().activeAccount();
}
const auto accounts = productionAccounts();
Expects(!accounts.empty());
return accounts.front();
}
auto ProxyRotationManager::find(
const MTP::ProxyData &proxy) -> Entry* {
const auto i = ranges::find(
_entries,
proxy,
[](const Entry &entry) { return entry.proxy; });
return (i == end(_entries)) ? nullptr : &*i;
}
auto ProxyRotationManager::ensure(
const MTP::ProxyData &proxy) -> Entry& {
if (const auto entry = find(proxy)) {
return *entry;
}
_entries.push_back({ .proxy = proxy });
return _entries.back();
}
void ProxyRotationManager::reevaluate() {
if (!shouldObserve()) {
stopChecking();
return;
}
const auto accounts = productionAccounts();
if (accounts.empty()) {
stopChecking();
return;
}
const auto stateProj = [](not_null<Main::Account*> account) {
return account->mtp().dcstate();
};
if (ranges::contains(accounts, MTP::ConnectedState, stateProj)) {
stopChecking();
return;
}
startChecking();
}
void ProxyRotationManager::startChecking() {
if (_checking) {
return;
}
_checking = true;
_waitingToSwitch = false;
_switchStartedAt = crl::now();
updateProbeOrder();
runChecks();
const auto timeout = App().settings().proxy().proxyRotationTimeout();
_switchTimer.callOnce(timeout * crl::time(1000));
}
void ProxyRotationManager::stopChecking() {
_checkTimer.cancel();
_switchTimer.cancel();
_checking = false;
_waitingToSwitch = false;
_switchStartedAt = 0;
_probeOrder.clear();
_nextCheckIndex = 0;
clearPendingChecks();
}
void ProxyRotationManager::pruneRemovedEntries() {
const auto &settings = App().settings().proxy();
_entries.erase(
std::remove_if(begin(_entries), end(_entries), [&](const Entry &entry) {
return (settings.indexInList(entry.proxy) < 0);
}),
end(_entries));
}
void ProxyRotationManager::updateProbeOrder() {
const auto &settings = App().settings().proxy();
const auto currentIndex = settings.indexInList(settings.selected());
_probeOrder.clear();
_probeOrder.reserve(settings.list().size());
for (const auto index : settings.proxyRotationPreferredIndices()) {
if (index == currentIndex) {
continue;
}
_probeOrder.push_back(index);
}
for (auto i = 0, count = int(settings.list().size()); i != count; ++i) {
if (i == currentIndex || ranges::contains(_probeOrder, i)) {
continue;
}
_probeOrder.push_back(i);
}
_nextCheckIndex = 0;
}
void ProxyRotationManager::continueChecking(crl::time delay) {
if (!_checking) {
return;
}
if (_checkTimer.isActive()) {
_checkTimer.cancel();
}
_checkTimer.callOnce(delay);
}
void ProxyRotationManager::runChecks() {
if (!_checking) {
return;
}
if (!shouldObserve()) {
stopChecking();
return;
}
const auto accounts = productionAccounts();
if (accounts.empty()
|| ranges::contains(
accounts,
MTP::ConnectedState,
[](not_null<Main::Account*> account) {
return account->mtp().dcstate();
})) {
stopChecking();
return;
}
pruneExpiredChecks();
startNextCheck();
continueChecking(kProxyRotationCheckInterval);
}
void ProxyRotationManager::pruneExpiredChecks() {
const auto now = crl::now();
for (auto &entry : _entries) {
if (!entry.checking
|| (now - entry.startedAt < kProxyRotationCheckLifetime)) {
continue;
}
MTP::ResetProxyCheckers(entry.v4, entry.v6);
entry.checking = false;
entry.startedAt = 0;
}
}
void ProxyRotationManager::startNextCheck() {
if (_probeOrder.empty()) {
return;
}
if (ranges::count(_entries, true, &Entry::checking)
>= kProxyRotationMaxActiveChecks) {
return;
}
const auto &settings = App().settings().proxy();
auto attemptsLeft = int(_probeOrder.size());
while (attemptsLeft-- > 0) {
if (_nextCheckIndex >= int(_probeOrder.size())) {
_nextCheckIndex = 0;
}
const auto listIndex = _probeOrder[_nextCheckIndex++];
if (listIndex < 0 || listIndex >= int(settings.list().size())) {
continue;
}
const auto &proxy = settings.list()[listIndex];
auto &entry = ensure(proxy);
if (entry.checking) {
continue;
}
entry.checking = true;
entry.startedAt = crl::now();
MTP::StartProxyCheck(
&accountForChecks()->mtp(),
proxy,
settings.tryIPv6(),
entry.v4,
entry.v6,
[=](MTP::details::AbstractConnection *raw, int ping) {
checkDone(proxy, raw, ping);
},
[=](MTP::details::AbstractConnection *raw) {
checkFailed(proxy, raw);
});
break;
}
}
void ProxyRotationManager::switchTimerDone() {
if (!_checking || !shouldSwitchToAvailable()) {
return;
}
_waitingToSwitch = !switchToAvailable();
}
void ProxyRotationManager::clearPendingChecks() {
for (auto &entry : _entries) {
MTP::ResetProxyCheckers(entry.v4, entry.v6);
entry.checking = false;
entry.startedAt = 0;
}
}
void ProxyRotationManager::checkDone(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw,
int ping) {
const auto entry = find(proxy);
if (!entry
|| !entry->checking
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
return;
}
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
MTP::ResetProxyCheckers(entry->v4, entry->v6);
entry->checking = false;
entry->startedAt = 0;
entry->availableAt = crl::now();
const auto proxySettings = &App().settings().proxy();
if (const auto index = proxySettings->indexInList(proxy); index >= 0) {
if (proxySettings->promoteProxyRotationPreferredIndex(index)) {
App().saveSettingsDelayed();
}
}
updateProbeOrder();
if (_waitingToSwitch && shouldSwitchToAvailable()) {
_waitingToSwitch = !switchToAvailable();
}
}
void ProxyRotationManager::checkFailed(
const MTP::ProxyData &proxy,
not_null<MTP::details::AbstractConnection*> raw) {
const auto entry = find(proxy);
if (!entry
|| !entry->checking
|| ((entry->v4.get() != raw) && (entry->v6.get() != raw))) {
return;
}
MTP::DropProxyChecker(entry->v4, entry->v6, raw);
if (MTP::HasProxyCheckers(entry->v4, entry->v6)) {
return;
}
entry->checking = false;
entry->startedAt = 0;
}
bool ProxyRotationManager::switchToAvailable() {
if (!_checking) {
return false;
}
const auto &settings = App().settings().proxy();
for (const auto index : _probeOrder) {
if (index < 0 || index >= int(settings.list().size())) {
continue;
}
const auto &proxy = settings.list()[index];
const auto entry = find(proxy);
if (!entry || entry->checking || !entry->availableAt) {
continue;
}
if (entry->availableAt < _switchStartedAt) {
continue;
}
_waitingToSwitch = false;
App().setCurrentProxy(proxy, MTP::ProxyData::Settings::Enabled);
App().saveSettingsDelayed();
return true;
}
return false;
}
bool ProxyRotationManager::shouldSwitchToAvailable() const {
if (!_checking || !shouldObserve()) {
return false;
}
const auto accounts = productionAccounts();
return !accounts.empty()
&& !ranges::contains(
accounts,
MTP::ConnectedState,
[](not_null<Main::Account*> account) {
return account->mtp().dcstate();
});
}
} // namespace Core