Check proxy availability in ProxiesBox.

This commit is contained in:
John Preston 2018-04-30 19:49:03 +04:00
parent 9935a36c3d
commit f794d8dbd8
15 changed files with 195 additions and 36 deletions

View File

@ -428,7 +428,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_online" = "online";
"lng_proxy_checking" = "checking";
"lng_proxy_connecting" = "connecting";
"lng_proxy_available" = "available";
"lng_proxy_available" = "available (ping: {ping}ms)";
"lng_proxy_unavailable" = "unavailable";
"lng_proxy_edit" = "Edit proxy";
"lng_proxy_undo_delete" = "Undo";

View File

@ -321,14 +321,7 @@ void Application::refreshGlobalProxy() {
}();
if (proxy.type == ProxyData::Type::Socks5
|| proxy.type == ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(QNetworkProxy(
(proxy.type == ProxyData::Type::Socks5
? QNetworkProxy::Socks5Proxy
: QNetworkProxy::HttpProxy),
proxy.host,
proxy.port,
proxy.user,
proxy.password));
QNetworkProxy::setApplicationProxy(ToNetworkProxy(proxy));
} else {
QNetworkProxyFactory::setUseSystemConfiguration(true);
}

View File

@ -732,7 +732,8 @@ proxyRowTitleStyle: TextStyle(defaultTextStyle) {
}
proxyRowStatusFg: windowSubTextFg;
proxyRowStatusFgOnline: windowActiveTextFg;
proxyRowStatusFgOffline: attentionButtonFg;
proxyRowStatusFgOffline: boxTextFgError;
proxyRowStatusFgAvailable: boxTextFgGood;
proxyRowEdit: IconButton(defaultIconButton) {
width: 40px;
height: 40px;

View File

@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "base/qthelp_url.h"
#include "mainwidget.h"
#include "messenger.h"
#include "mainwindow.h"
#include "auth_session.h"
#include "data/data_session.h"
#include "mtproto/connection.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
@ -39,6 +41,7 @@ constexpr auto kSaveSettingsDelayedTimeout = TimeMs(1000);
class ProxyRow : public Ui::RippleButton {
public:
using View = ProxiesBoxController::ItemView;
using State = ProxiesBoxController::ItemState;
ProxyRow(QWidget *parent, View &&view);
@ -250,25 +253,29 @@ void ProxyRow::paintEvent(QPaintEvent *e) {
const auto statusFg = [&] {
switch (_view.state) {
case View::State::Online:
case State::Online:
return st::proxyRowStatusFgOnline;
case View::State::Unavailable:
case State::Unavailable:
return st::proxyRowStatusFgOffline;
case State::Available:
return st::proxyRowStatusFgAvailable;
default:
return st::proxyRowStatusFg;
}
}();
const auto status = [&] {
switch (_view.state) {
case View::State::Available:
return lang(lng_proxy_available);
case View::State::Checking:
return lang(lng_proxy_available);
case View::State::Connecting:
case State::Available:
return lng_proxy_available(
lt_ping,
QString::number(_view.ping));
case State::Checking:
return lang(lng_proxy_checking);
case State::Connecting:
return lang(lng_proxy_connecting);
case View::State::Online:
case State::Online:
return lang(lng_proxy_online);
case View::State::Unavailable:
case State::Unavailable:
return lang(lng_proxy_unavailable);
}
Unexpected("State in ProxyRow::paintEvent.");
@ -1030,6 +1037,100 @@ ProxiesBoxController::ProxiesBoxController()
) | ranges::view::transform([&](const ProxyData &proxy) {
return Item{ ++_idCounter, proxy };
}) | ranges::to_vector;
for (auto &item : _list) {
if (!Global::UseProxy() || item.data != Global::SelectedProxy()) {
createChecker(item);
}
}
}
void ProxiesBoxController::createChecker(Item &item) {
using Variants = MTP::DcOptions::Variants;
const auto type = (item.data.type == ProxyData::Type::Http)
? Variants::Http
: Variants::Tcp;
const auto mtproto = Messenger::Instance().mtp();
const auto dcId = mtproto->mainDcId();
item.state = ItemState::Checking;
const auto setup = [&](Checker &checker) {
checker = MTP::internal::AbstractConnection::create(
type,
QThread::currentThread());
setupChecker(item.id, checker);
};
setup(item.checker);
if (item.data.type == ProxyData::Type::Mtproto) {
item.checkerv6 = nullptr;
item.checker->connectToServer(
item.data.host,
item.data.port,
MTP::ProtocolSecretFromPassword(item.data.password),
dcId);
} else {
const auto options = mtproto->dcOptions()->lookup(
dcId,
MTP::DcType::Regular,
true);
const auto endpoint = options.data[Variants::IPv4][type];
const auto endpointv6 = options.data[Variants::IPv6][type];
if (endpoint.empty()) {
item.checker = nullptr;
}
if (Global::TryIPv6() && !endpointv6.empty()) {
setup(item.checkerv6);
} else {
item.checkerv6 = nullptr;
}
if (!item.checker && !item.checkerv6) {
item.state = ItemState::Unavailable;
return;
}
const auto connect = [&](
const Checker &checker,
const std::vector<MTP::DcOptions::Endpoint> &endpoints) {
if (checker) {
checker->setProxyOverride(item.data);
checker->connectToServer(
QString::fromStdString(endpoints.front().ip),
endpoints.front().port,
endpoints.front().protocolSecret,
dcId);
}
};
connect(item.checker, endpoint);
connect(item.checkerv6, endpointv6);
}
}
void ProxiesBoxController::setupChecker(int id, const Checker &checker) {
using Connection = MTP::internal::AbstractConnection;
const auto pointer = checker.get();
pointer->connect(pointer, &Connection::connected, [=] {
const auto item = findById(id);
const auto pingTime = pointer->pingTime();
item->checker = nullptr;
item->checkerv6 = nullptr;
if (item->state == ItemState::Checking) {
item->state = ItemState::Available;
item->ping = pingTime;
updateView(*item);
}
});
const auto failed = [=] {
const auto item = findById(id);
if (item->checker == pointer) {
item->checker = nullptr;
} else if (item->checkerv6 == pointer) {
item->checkerv6 = nullptr;
}
if (!item->checker && !item->checkerv6 && item->state == ItemState::Checking) {
item->state = ItemState::Unavailable;
updateView(*item);
}
};
pointer->connect(pointer, &Connection::disconnected, failed);
pointer->connect(pointer, &Connection::error, failed);
}
object_ptr<BoxContent> ProxiesBoxController::CreateOwningBox() {
@ -1207,6 +1308,7 @@ void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
proxies.push_back(proxy);
_list.push_back({ ++_idCounter, proxy });
createChecker(_list.back());
applyItem(_list.back().id);
}
@ -1251,7 +1353,6 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
}
void ProxiesBoxController::updateView(const Item &item) {
const auto state = ItemView::State::Checking;
const auto ping = 0;
const auto selected = (Global::SelectedProxy() == item.data);
const auto deleted = item.deleted;
@ -1271,10 +1372,10 @@ void ProxiesBoxController::updateView(const Item &item) {
type,
item.data.host,
item.data.port,
ping,
item.ping,
!deleted && selected,
deleted,
state });
item.state });
}
ProxiesBoxController::~ProxiesBoxController() {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "mtproto/connection_abstract.h"
namespace Ui {
class InputField;
@ -98,15 +99,14 @@ public:
static object_ptr<BoxContent> CreateOwningBox();
object_ptr<BoxContent> create();
enum class ItemState {
Connecting,
Online,
Checking,
Available,
Unavailable
};
struct ItemView {
enum class State {
Connecting,
Online,
Checking,
Available,
Unavailable
};
int id = 0;
QString type;
QString host;
@ -114,7 +114,7 @@ public:
int ping = 0;
bool selected = false;
bool deleted = false;
State state = State::Checking;
ItemState state = ItemState::Checking;
};
@ -131,10 +131,16 @@ public:
~ProxiesBoxController();
private:
using Checker = MTP::internal::ConnectionPointer;
struct Item {
int id = 0;
ProxyData data;
bool deleted = false;
Checker checker;
Checker checkerv6;
ItemState state = ItemState::Checking;
int ping = 0;
};
std::vector<Item>::iterator findById(int id);
@ -143,6 +149,8 @@ private:
void updateView(const Item &item);
void applyChanges();
void saveDelayed();
void createChecker(Item &item);
void setupChecker(int id, const Checker &checker);
void replaceItemWith(
std::vector<Item>::iterator which,

View File

@ -268,6 +268,22 @@ bool ProxyData::ValidSecret(const QString &secret) {
return QRegularExpression("^[a-fA-F0-9]{32}$").match(secret).hasMatch();
}
QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
Expects(proxy.type != ProxyData::Type::Mtproto);
if (proxy.type == ProxyData::Type::None) {
return QNetworkProxy::NoProxy;
}
return QNetworkProxy(
(proxy.type == ProxyData::Type::Socks5
? QNetworkProxy::Socks5Proxy
: QNetworkProxy::HttpProxy),
proxy.host,
proxy.port,
proxy.user,
proxy.password);
}
namespace ThirdParty {
void start() {

View File

@ -442,6 +442,8 @@ struct ProxyData {
};
QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
enum DBIScale {
dbisAuto = 0,
dbisOne = 1,

View File

@ -3215,4 +3215,8 @@ std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::con
return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes);
}
bytes::vector ProtocolSecretFromPassword(const QString &password) {
return internal::ProtocolSecretFromPassword(password);
}
} // namespace MTP

View File

@ -26,6 +26,8 @@ struct ModExpFirst {
ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed);
std::vector<gsl::byte> CreateAuthKey(base::const_byte_span firstBytes, base::const_byte_span randomBytes, base::const_byte_span primeBytes);
bytes::vector ProtocolSecretFromPassword(const QString &password);
namespace internal {
class AbstractConnection;

View File

@ -122,6 +122,10 @@ MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) {
return response;
}
AbstractConnection::AbstractConnection(QThread *thread) {
moveToThread(thread);
}
ConnectionPointer AbstractConnection::create(
DcOptions::Variants::Protocol protocol,
QThread *thread) {

View File

@ -43,9 +43,7 @@ class AbstractConnection : public QObject {
Q_OBJECT
public:
AbstractConnection(QThread *thread) : _sentEncrypted(false) {
moveToThread(thread);
}
AbstractConnection(QThread *thread);
AbstractConnection(const AbstractConnection &other) = delete;
AbstractConnection &operator=(const AbstractConnection &other) = delete;
virtual ~AbstractConnection() = 0;
@ -59,6 +57,8 @@ public:
_sentEncrypted = true;
}
virtual void setProxyOverride(const ProxyData &proxy) = 0;
virtual TimeMs pingTime() const = 0;
virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
virtual void disconnectFromServer() = 0;
virtual void connectToServer(
@ -98,7 +98,8 @@ signals:
protected:
BuffersQueue _receivedQueue; // list of received packets, not processed yet
bool _sentEncrypted;
bool _sentEncrypted = false;
int _pingTime = 0;
// first we always send fake MTPReq_pq to see if connection works at all
// we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one

View File

@ -87,6 +87,10 @@ HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread)
manager.moveToThread(thread);
}
void HTTPConnection::setProxyOverride(const ProxyData &proxy) {
manager.setProxy(ToNetworkProxy(proxy));
}
void HTTPConnection::sendData(mtpBuffer &buffer) {
if (status == FinishedWork) return;
@ -134,6 +138,7 @@ void HTTPConnection::connectToServer(
DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP transport to %1").arg(ip));
_pingTime = getms();
sendData(buffer);
}
@ -162,6 +167,7 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
if (res_pq_data.vnonce == httpNonce) {
DEBUG_LOG(("Connection Info: HTTP-transport to %1 connected by pq-response").arg(_address));
status = UsingHttp;
_pingTime = getms() - _pingTime;
emit connected();
}
} catch (Exception &e) {
@ -179,6 +185,10 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
}
}
TimeMs HTTPConnection::pingTime() const {
return isConnected() ? _pingTime : TimeMs(0);
}
bool HTTPConnection::usingHttpWait() {
return true;
}

View File

@ -18,6 +18,8 @@ class HTTPConnection : public AbstractConnection {
public:
HTTPConnection(QThread *thread);
void setProxyOverride(const ProxyData &proxy) override;
TimeMs pingTime() const override;
void sendData(mtpBuffer &buffer) override;
void disconnectFromServer() override;
void connectToServer(
@ -57,6 +59,8 @@ private:
typedef QSet<QNetworkReply*> Requests;
Requests requests;
TimeMs _pingTime = 0;
};
} // namespace internal

View File

@ -37,9 +37,12 @@ AbstractTCPConnection::AbstractTCPConnection(
, currentPos((char*)shortBuffer) {
}
AbstractTCPConnection::~AbstractTCPConnection() {
void AbstractTCPConnection::setProxyOverride(const ProxyData &proxy) {
sock.setProxy(ToNetworkProxy(proxy));
}
AbstractTCPConnection::~AbstractTCPConnection() = default;
void AbstractTCPConnection::socketRead() {
if (sock.state() != QAbstractSocket::ConnectedState) {
LOG(("MTP error: socket not connected in socketRead(), state: %1").arg(sock.state()));
@ -218,6 +221,7 @@ void TCPConnection::onSocketConnected() {
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
tcpTimeoutTimer.start(_tcpTimeout);
_pingTime = getms();
sendData(buffer);
}
}
@ -379,6 +383,10 @@ void TCPConnection::connectToServer(
sock.connectToHost(QHostAddress(_address), _port);
}
TimeMs TCPConnection::pingTime() const {
return isConnected() ? _pingTime : TimeMs(0);
}
void TCPConnection::socketPacket(const char *packet, uint32 length) {
if (status == FinishedWork) return;
@ -396,6 +404,7 @@ void TCPConnection::socketPacket(const char *packet, uint32 length) {
if (res_pq_data.vnonce == tcpNonce) {
DEBUG_LOG(("Connection Info: TCP-transport to %1 chosen by pq-response").arg(_address));
status = UsingTcp;
_pingTime = (getms() - _pingTime);
emit connected();
}
} catch (Exception &e) {

View File

@ -18,6 +18,8 @@ class AbstractTCPConnection : public AbstractConnection {
public:
AbstractTCPConnection(QThread *thread);
void setProxyOverride(const ProxyData &proxy) override;
virtual ~AbstractTCPConnection() = 0;
public slots:
@ -60,6 +62,7 @@ class TCPConnection : public AbstractTCPConnection {
public:
TCPConnection(QThread *thread);
TimeMs pingTime() const override;
void sendData(mtpBuffer &buffer) override;
void disconnectFromServer() override;
void connectToServer(
@ -97,6 +100,7 @@ private:
QString _address;
int32 _port, _tcpTimeout;
QTimer tcpTimeoutTimer;
TimeMs _pingTime = 0;
};