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_online" = "online";
"lng_proxy_checking" = "checking"; "lng_proxy_checking" = "checking";
"lng_proxy_connecting" = "connecting"; "lng_proxy_connecting" = "connecting";
"lng_proxy_available" = "available"; "lng_proxy_available" = "available (ping: {ping}ms)";
"lng_proxy_unavailable" = "unavailable"; "lng_proxy_unavailable" = "unavailable";
"lng_proxy_edit" = "Edit proxy"; "lng_proxy_edit" = "Edit proxy";
"lng_proxy_undo_delete" = "Undo"; "lng_proxy_undo_delete" = "Undo";

View File

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

View File

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

View File

@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "messenger.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "auth_session.h" #include "auth_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "mtproto/connection.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
@ -39,6 +41,7 @@ constexpr auto kSaveSettingsDelayedTimeout = TimeMs(1000);
class ProxyRow : public Ui::RippleButton { class ProxyRow : public Ui::RippleButton {
public: public:
using View = ProxiesBoxController::ItemView; using View = ProxiesBoxController::ItemView;
using State = ProxiesBoxController::ItemState;
ProxyRow(QWidget *parent, View &&view); ProxyRow(QWidget *parent, View &&view);
@ -250,25 +253,29 @@ void ProxyRow::paintEvent(QPaintEvent *e) {
const auto statusFg = [&] { const auto statusFg = [&] {
switch (_view.state) { switch (_view.state) {
case View::State::Online: case State::Online:
return st::proxyRowStatusFgOnline; return st::proxyRowStatusFgOnline;
case View::State::Unavailable: case State::Unavailable:
return st::proxyRowStatusFgOffline; return st::proxyRowStatusFgOffline;
case State::Available:
return st::proxyRowStatusFgAvailable;
default: default:
return st::proxyRowStatusFg; return st::proxyRowStatusFg;
} }
}(); }();
const auto status = [&] { const auto status = [&] {
switch (_view.state) { switch (_view.state) {
case View::State::Available: case State::Available:
return lang(lng_proxy_available); return lng_proxy_available(
case View::State::Checking: lt_ping,
return lang(lng_proxy_available); QString::number(_view.ping));
case View::State::Connecting: case State::Checking:
return lang(lng_proxy_checking);
case State::Connecting:
return lang(lng_proxy_connecting); return lang(lng_proxy_connecting);
case View::State::Online: case State::Online:
return lang(lng_proxy_online); return lang(lng_proxy_online);
case View::State::Unavailable: case State::Unavailable:
return lang(lng_proxy_unavailable); return lang(lng_proxy_unavailable);
} }
Unexpected("State in ProxyRow::paintEvent."); Unexpected("State in ProxyRow::paintEvent.");
@ -1030,6 +1037,100 @@ ProxiesBoxController::ProxiesBoxController()
) | ranges::view::transform([&](const ProxyData &proxy) { ) | ranges::view::transform([&](const ProxyData &proxy) {
return Item{ ++_idCounter, proxy }; return Item{ ++_idCounter, proxy };
}) | ranges::to_vector; }) | 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() { object_ptr<BoxContent> ProxiesBoxController::CreateOwningBox() {
@ -1207,6 +1308,7 @@ void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
proxies.push_back(proxy); proxies.push_back(proxy);
_list.push_back({ ++_idCounter, proxy }); _list.push_back({ ++_idCounter, proxy });
createChecker(_list.back());
applyItem(_list.back().id); applyItem(_list.back().id);
} }
@ -1251,7 +1353,6 @@ auto ProxiesBoxController::views() const -> rpl::producer<ItemView> {
} }
void ProxiesBoxController::updateView(const Item &item) { void ProxiesBoxController::updateView(const Item &item) {
const auto state = ItemView::State::Checking;
const auto ping = 0; const auto ping = 0;
const auto selected = (Global::SelectedProxy() == item.data); const auto selected = (Global::SelectedProxy() == item.data);
const auto deleted = item.deleted; const auto deleted = item.deleted;
@ -1271,10 +1372,10 @@ void ProxiesBoxController::updateView(const Item &item) {
type, type,
item.data.host, item.data.host,
item.data.port, item.data.port,
ping, item.ping,
!deleted && selected, !deleted && selected,
deleted, deleted,
state }); item.state });
} }
ProxiesBoxController::~ProxiesBoxController() { ProxiesBoxController::~ProxiesBoxController() {

View File

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

View File

@ -442,6 +442,8 @@ struct ProxyData {
}; };
QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
enum DBIScale { enum DBIScale {
dbisAuto = 0, dbisAuto = 0,
dbisOne = 1, 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); return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes);
} }
bytes::vector ProtocolSecretFromPassword(const QString &password) {
return internal::ProtocolSecretFromPassword(password);
}
} // namespace MTP } // namespace MTP

View File

@ -26,6 +26,8 @@ struct ModExpFirst {
ModExpFirst CreateModExp(int g, base::const_byte_span primeBytes, base::const_byte_span randomSeed); 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); 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 { namespace internal {
class AbstractConnection; class AbstractConnection;

View File

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

View File

@ -43,9 +43,7 @@ class AbstractConnection : public QObject {
Q_OBJECT Q_OBJECT
public: public:
AbstractConnection(QThread *thread) : _sentEncrypted(false) { AbstractConnection(QThread *thread);
moveToThread(thread);
}
AbstractConnection(const AbstractConnection &other) = delete; AbstractConnection(const AbstractConnection &other) = delete;
AbstractConnection &operator=(const AbstractConnection &other) = delete; AbstractConnection &operator=(const AbstractConnection &other) = delete;
virtual ~AbstractConnection() = 0; virtual ~AbstractConnection() = 0;
@ -59,6 +57,8 @@ public:
_sentEncrypted = true; _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 sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
virtual void disconnectFromServer() = 0; virtual void disconnectFromServer() = 0;
virtual void connectToServer( virtual void connectToServer(
@ -98,7 +98,8 @@ signals:
protected: protected:
BuffersQueue _receivedQueue; // list of received packets, not processed yet 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 // 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 // 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); manager.moveToThread(thread);
} }
void HTTPConnection::setProxyOverride(const ProxyData &proxy) {
manager.setProxy(ToNetworkProxy(proxy));
}
void HTTPConnection::sendData(mtpBuffer &buffer) { void HTTPConnection::sendData(mtpBuffer &buffer) {
if (status == FinishedWork) return; 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)); DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP transport to %1").arg(ip));
_pingTime = getms();
sendData(buffer); sendData(buffer);
} }
@ -162,6 +167,7 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
if (res_pq_data.vnonce == httpNonce) { if (res_pq_data.vnonce == httpNonce) {
DEBUG_LOG(("Connection Info: HTTP-transport to %1 connected by pq-response").arg(_address)); DEBUG_LOG(("Connection Info: HTTP-transport to %1 connected by pq-response").arg(_address));
status = UsingHttp; status = UsingHttp;
_pingTime = getms() - _pingTime;
emit connected(); emit connected();
} }
} catch (Exception &e) { } catch (Exception &e) {
@ -179,6 +185,10 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) {
} }
} }
TimeMs HTTPConnection::pingTime() const {
return isConnected() ? _pingTime : TimeMs(0);
}
bool HTTPConnection::usingHttpWait() { bool HTTPConnection::usingHttpWait() {
return true; return true;
} }

View File

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

View File

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

View File

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