2017-06-26 17:38:16 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-06-26 17:38:16 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-06-26 17:38:16 +00:00
|
|
|
*/
|
|
|
|
#include "mtproto/special_config_request.h"
|
|
|
|
|
|
|
|
#include "mtproto/rsa_public_key.h"
|
|
|
|
#include "mtproto/dc_options.h"
|
|
|
|
#include "mtproto/auth_key.h"
|
|
|
|
#include "base/openssl_help.h"
|
2018-06-06 12:16:21 +00:00
|
|
|
|
|
|
|
extern "C" {
|
2017-06-26 17:38:16 +00:00
|
|
|
#include <openssl/aes.h>
|
2018-06-06 12:16:21 +00:00
|
|
|
} // extern "C"
|
2017-06-26 17:38:16 +00:00
|
|
|
|
|
|
|
namespace MTP {
|
|
|
|
namespace {
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
struct DnsEntry {
|
|
|
|
QString data;
|
2019-03-01 14:41:10 +00:00
|
|
|
crl::time TTL = 0;
|
2018-05-17 19:58:00 +00:00
|
|
|
};
|
|
|
|
|
2019-02-19 06:57:53 +00:00
|
|
|
constexpr auto kSendNextTimeout = crl::time(1000);
|
|
|
|
constexpr auto kMinTimeToLive = 10 * crl::time(1000);
|
|
|
|
constexpr auto kMaxTimeToLive = 300 * crl::time(1000);
|
2018-05-03 08:11:07 +00:00
|
|
|
|
2017-06-26 17:38:16 +00:00
|
|
|
constexpr auto kPublicKey = str_const("\
|
|
|
|
-----BEGIN RSA PUBLIC KEY-----\n\
|
|
|
|
MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\
|
|
|
|
fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n\
|
|
|
|
192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n\
|
|
|
|
9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n\
|
|
|
|
fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n\
|
|
|
|
Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\
|
|
|
|
-----END RSA PUBLIC KEY-----\
|
|
|
|
");
|
|
|
|
|
2018-05-03 08:11:07 +00:00
|
|
|
constexpr auto kUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
const auto &DnsDomains() {
|
|
|
|
static auto result = std::vector<QString>{
|
|
|
|
qsl("google.com"),
|
|
|
|
qsl("www.google.com"),
|
|
|
|
qsl("google.ru"),
|
|
|
|
qsl("www.google.ru"),
|
|
|
|
};
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-05-02 19:27:03 +00:00
|
|
|
bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) {
|
2018-05-05 18:54:54 +00:00
|
|
|
const auto check = QString(phone).replace(
|
|
|
|
QRegularExpression("[^0-9]"),
|
|
|
|
QString());
|
2018-05-02 19:27:03 +00:00
|
|
|
auto result = false;
|
|
|
|
for (const auto &prefix : rules.split(',')) {
|
|
|
|
if (prefix.isEmpty()) {
|
|
|
|
result = true;
|
2018-05-05 18:54:54 +00:00
|
|
|
} else if (prefix[0] == '+' && check.startsWith(prefix.mid(1))) {
|
2018-05-02 19:27:03 +00:00
|
|
|
result = true;
|
2018-05-05 18:54:54 +00:00
|
|
|
} else if (prefix[0] == '-' && check.startsWith(prefix.mid(1))) {
|
2018-05-02 19:27:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-01-30 18:10:53 +00:00
|
|
|
QString GenerateRandomPadding() {
|
|
|
|
constexpr char kValid[] = "abcdefghijklmnopqrstuvwxyz"
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
|
|
|
|
|
|
auto result = QString();
|
|
|
|
const auto count = [&] {
|
|
|
|
constexpr auto kMinPadding = 13;
|
|
|
|
constexpr auto kMaxPadding = 128;
|
|
|
|
while (true) {
|
|
|
|
const auto result = 1 + (rand_value<uchar>() / 2);
|
|
|
|
Assert(result <= kMaxPadding);
|
|
|
|
if (result >= kMinPadding) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
result.resize(count);
|
|
|
|
for (auto &ch : result) {
|
|
|
|
ch = kValid[rand_value<uchar>() % (sizeof(kValid) - 1)];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<DnsEntry> ParseDnsResponse(
|
|
|
|
const QByteArray &bytes,
|
|
|
|
std::optional<int> typeRestriction = std::nullopt) {
|
2018-05-17 19:58:00 +00:00
|
|
|
// Read and store to "result" all the data bytes from the response:
|
2018-05-03 08:11:07 +00:00
|
|
|
// { ..,
|
|
|
|
// "Answer": [
|
2018-05-17 19:58:00 +00:00
|
|
|
// { .., "data": "bytes1", "TTL": int, .. },
|
|
|
|
// { .., "data": "bytes2", "TTL": int, .. }
|
2018-05-03 08:11:07 +00:00
|
|
|
// ],
|
|
|
|
// .. }
|
|
|
|
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
2019-01-30 18:10:53 +00:00
|
|
|
const auto document = QJsonDocument::fromJson(bytes, &error);
|
2017-06-26 17:38:16 +00:00
|
|
|
if (error.error != QJsonParseError::NoError) {
|
2018-05-03 08:11:07 +00:00
|
|
|
LOG(("Config Error: Failed to parse dns response JSON, error: %1"
|
|
|
|
).arg(error.errorString()));
|
2019-01-30 18:10:53 +00:00
|
|
|
return {};
|
2017-06-26 17:38:16 +00:00
|
|
|
} else if (!document.isObject()) {
|
|
|
|
LOG(("Config Error: Not an object received in dns response JSON."));
|
2019-01-30 18:10:53 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
const auto response = document.object();
|
|
|
|
const auto answerIt = response.find(qsl("Answer"));
|
|
|
|
if (answerIt == response.constEnd()) {
|
|
|
|
LOG(("Config Error: Could not find Answer in dns response JSON."));
|
|
|
|
return {};
|
|
|
|
} else if (!(*answerIt).isArray()) {
|
|
|
|
LOG(("Config Error: Not an array received "
|
|
|
|
"in Answer in dns response JSON."));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
auto result = std::vector<DnsEntry>();
|
|
|
|
for (const auto elem : (*answerIt).toArray()) {
|
|
|
|
if (!elem.isObject()) {
|
|
|
|
LOG(("Config Error: Not an object found "
|
|
|
|
"in Answer array in dns response JSON."));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const auto object = elem.toObject();
|
|
|
|
if (typeRestriction) {
|
|
|
|
const auto typeIt = object.find(qsl("type"));
|
|
|
|
const auto type = int(std::round((*typeIt).toDouble()));
|
|
|
|
if (!(*typeIt).isDouble()) {
|
|
|
|
LOG(("Config Error: Not a number in type field "
|
|
|
|
"in Answer array in dns response JSON."));
|
|
|
|
continue;
|
|
|
|
} else if (type != *typeRestriction) {
|
|
|
|
continue;
|
2017-06-26 17:38:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-30 18:10:53 +00:00
|
|
|
const auto dataIt = object.find(qsl("data"));
|
|
|
|
if (dataIt == object.constEnd()) {
|
|
|
|
LOG(("Config Error: Could not find data "
|
|
|
|
"in Answer array entry in dns response JSON."));
|
|
|
|
continue;
|
|
|
|
} else if (!(*dataIt).isString()) {
|
|
|
|
LOG(("Config Error: Not a string data found "
|
|
|
|
"in Answer array entry in dns response JSON."));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto ttlIt = object.find(qsl("TTL"));
|
|
|
|
const auto ttl = (ttlIt != object.constEnd())
|
2019-03-01 14:41:10 +00:00
|
|
|
? crl::time(std::round((*ttlIt).toDouble()))
|
|
|
|
: crl::time(0);
|
2019-01-30 18:10:53 +00:00
|
|
|
result.push_back({ (*dataIt).toString(), ttl });
|
2017-06-26 17:38:16 +00:00
|
|
|
}
|
2018-05-17 19:58:00 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray ConcatenateDnsTxtFields(const std::vector<DnsEntry> &response) {
|
|
|
|
auto entries = QMap<int, QString>();
|
|
|
|
for (const auto &entry : response) {
|
|
|
|
entries.insertMulti(INT_MAX - entry.data.size(), entry.data);
|
|
|
|
}
|
2018-05-03 08:11:07 +00:00
|
|
|
return QStringList(entries.values()).join(QString()).toLatin1();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
ServiceWebRequest::ServiceWebRequest(not_null<QNetworkReply*> reply)
|
2018-05-03 08:11:07 +00:00
|
|
|
: reply(reply.get()) {
|
|
|
|
}
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
ServiceWebRequest::ServiceWebRequest(ServiceWebRequest &&other)
|
2018-05-03 08:11:07 +00:00
|
|
|
: reply(base::take(other.reply)) {
|
|
|
|
}
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
ServiceWebRequest &ServiceWebRequest::operator=(ServiceWebRequest &&other) {
|
2018-05-03 08:11:07 +00:00
|
|
|
if (reply != other.reply) {
|
|
|
|
destroy();
|
|
|
|
reply = base::take(other.reply);
|
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
void ServiceWebRequest::destroy() {
|
2018-05-03 08:11:07 +00:00
|
|
|
if (const auto value = base::take(reply)) {
|
2018-06-06 09:32:38 +00:00
|
|
|
value->disconnect(
|
|
|
|
value,
|
|
|
|
&QNetworkReply::finished,
|
|
|
|
nullptr,
|
|
|
|
nullptr);
|
2018-05-03 08:11:07 +00:00
|
|
|
value->abort();
|
2018-06-06 09:32:38 +00:00
|
|
|
value->deleteLater();
|
2018-05-03 08:11:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-17 19:58:00 +00:00
|
|
|
ServiceWebRequest::~ServiceWebRequest() {
|
2018-06-03 13:30:40 +00:00
|
|
|
if (reply) {
|
|
|
|
reply->deleteLater();
|
|
|
|
}
|
2018-05-03 08:11:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SpecialConfigRequest::SpecialConfigRequest(
|
2018-06-04 15:35:11 +00:00
|
|
|
Fn<void(
|
2018-05-03 08:11:07 +00:00
|
|
|
DcId dcId,
|
|
|
|
const std::string &ip,
|
|
|
|
int port,
|
|
|
|
bytes::const_span secret)> callback,
|
|
|
|
const QString &phone)
|
|
|
|
: _callback(std::move(callback))
|
|
|
|
, _phone(phone) {
|
2018-05-17 19:58:00 +00:00
|
|
|
_manager.setProxy(QNetworkProxy::NoProxy);
|
2018-05-03 08:11:07 +00:00
|
|
|
_attempts = {
|
|
|
|
{ Type::App, qsl("software-download.microsoft.com") },
|
|
|
|
};
|
2018-05-17 19:58:00 +00:00
|
|
|
for (const auto &domain : DnsDomains()) {
|
|
|
|
_attempts.push_back({ Type::Dns, domain });
|
|
|
|
}
|
2018-05-03 08:11:07 +00:00
|
|
|
std::random_device rd;
|
|
|
|
ranges::shuffle(_attempts, std::mt19937(rd()));
|
|
|
|
sendNextRequest();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpecialConfigRequest::sendNextRequest() {
|
|
|
|
Expects(!_attempts.empty());
|
|
|
|
|
|
|
|
const auto attempt = _attempts.back();
|
|
|
|
_attempts.pop_back();
|
|
|
|
if (!_attempts.empty()) {
|
|
|
|
App::CallDelayed(kSendNextTimeout, this, [=] {
|
|
|
|
sendNextRequest();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
performRequest(attempt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpecialConfigRequest::performRequest(const Attempt &attempt) {
|
|
|
|
const auto type = attempt.type;
|
|
|
|
auto url = QUrl();
|
|
|
|
url.setScheme(qsl("https"));
|
|
|
|
url.setHost(attempt.domain);
|
|
|
|
auto request = QNetworkRequest();
|
|
|
|
switch (type) {
|
|
|
|
case Type::App: {
|
|
|
|
url.setPath(cTestMode()
|
2018-05-11 14:03:53 +00:00
|
|
|
? qsl("/testv2/config.txt")
|
2018-05-03 08:11:07 +00:00
|
|
|
: qsl("/prodv2/config.txt"));
|
|
|
|
request.setRawHeader("Host", "tcdnb.azureedge.net");
|
|
|
|
} break;
|
|
|
|
case Type::Dns: {
|
|
|
|
url.setPath(qsl("/resolve"));
|
2019-01-30 18:10:53 +00:00
|
|
|
url.setQuery(qsl("name=%1&type=ANY&random_padding=%2"
|
|
|
|
).arg(Global::TxtDomainString()
|
|
|
|
).arg(GenerateRandomPadding()));
|
2018-05-03 08:11:07 +00:00
|
|
|
request.setRawHeader("Host", "dns.google.com");
|
|
|
|
} break;
|
|
|
|
default: Unexpected("Type in SpecialConfigRequest::performRequest.");
|
|
|
|
}
|
|
|
|
request.setUrl(url);
|
|
|
|
request.setRawHeader("User-Agent", kUserAgent);
|
|
|
|
const auto reply = _requests.emplace_back(
|
|
|
|
_manager.get(request)
|
|
|
|
).reply;
|
|
|
|
connect(reply, &QNetworkReply::finished, this, [=] {
|
|
|
|
requestFinished(type, reply);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpecialConfigRequest::requestFinished(
|
|
|
|
Type type,
|
|
|
|
not_null<QNetworkReply*> reply) {
|
|
|
|
const auto result = finalizeRequest(reply);
|
|
|
|
switch (type) {
|
|
|
|
case Type::App: handleResponse(result); break;
|
2019-01-30 18:10:53 +00:00
|
|
|
case Type::Dns: {
|
|
|
|
constexpr auto kTypeRestriction = 16; // TXT
|
|
|
|
handleResponse(ConcatenateDnsTxtFields(
|
|
|
|
ParseDnsResponse(result, kTypeRestriction)));
|
|
|
|
} break;
|
2018-05-03 08:11:07 +00:00
|
|
|
default: Unexpected("Type in SpecialConfigRequest::requestFinished.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray SpecialConfigRequest::finalizeRequest(
|
|
|
|
not_null<QNetworkReply*> reply) {
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
2018-06-04 15:35:11 +00:00
|
|
|
LOG(("Config Error: Failed to get response, error: %2 (%3)"
|
2018-05-03 08:11:07 +00:00
|
|
|
).arg(reply->errorString()
|
|
|
|
).arg(reply->error()));
|
|
|
|
}
|
|
|
|
const auto result = reply->readAll();
|
|
|
|
const auto from = ranges::remove(
|
|
|
|
_requests,
|
|
|
|
reply,
|
2018-05-17 19:58:00 +00:00
|
|
|
[](const ServiceWebRequest &request) { return request.reply; });
|
2018-05-03 08:11:07 +00:00
|
|
|
_requests.erase(from, end(_requests));
|
|
|
|
return result;
|
2017-06-26 17:38:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) {
|
|
|
|
auto cleanBytes = bytes;
|
|
|
|
auto removeFrom = std::remove_if(cleanBytes.begin(), cleanBytes.end(), [](char ch) {
|
|
|
|
auto isGoodBase64 = (ch == '+') || (ch == '=') || (ch == '/')
|
|
|
|
|| (ch >= 'a' && ch <= 'z')
|
|
|
|
|| (ch >= 'A' && ch <= 'Z')
|
|
|
|
|| (ch >= '0' && ch <= '9');
|
|
|
|
return !isGoodBase64;
|
|
|
|
});
|
|
|
|
if (removeFrom != cleanBytes.end()) {
|
|
|
|
cleanBytes.remove(removeFrom - cleanBytes.begin(), cleanBytes.end() - removeFrom);
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr auto kGoodSizeBase64 = 344;
|
|
|
|
if (cleanBytes.size() != kGoodSizeBase64) {
|
|
|
|
LOG(("Config Error: Bad data size %1 required %2").arg(cleanBytes.size()).arg(kGoodSizeBase64));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
constexpr auto kGoodSizeData = 256;
|
|
|
|
auto decodedBytes = QByteArray::fromBase64(cleanBytes, QByteArray::Base64Encoding);
|
|
|
|
if (decodedBytes.size() != kGoodSizeData) {
|
|
|
|
LOG(("Config Error: Bad data size %1 required %2").arg(decodedBytes.size()).arg(kGoodSizeData));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-27 12:16:00 +00:00
|
|
|
auto publicKey = internal::RSAPublicKey(bytes::make_span(
|
2017-12-08 15:24:09 +00:00
|
|
|
kPublicKey.c_str(),
|
2018-03-27 12:16:00 +00:00
|
|
|
kPublicKey.size()));
|
|
|
|
auto decrypted = publicKey.decrypt(bytes::make_span(decodedBytes));
|
2017-06-26 17:38:16 +00:00
|
|
|
auto decryptedBytes = gsl::make_span(decrypted);
|
|
|
|
|
2018-03-27 12:16:00 +00:00
|
|
|
auto aesEncryptedBytes = decryptedBytes.subspan(CTRState::KeySize);
|
|
|
|
auto aesivec = bytes::make_vector(decryptedBytes.subspan(CTRState::KeySize - CTRState::IvecSize, CTRState::IvecSize));
|
2017-06-26 17:38:16 +00:00
|
|
|
AES_KEY aeskey;
|
2018-03-27 12:16:00 +00:00
|
|
|
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(decryptedBytes.data()), CTRState::KeySize * CHAR_BIT, &aeskey);
|
2017-06-26 17:38:16 +00:00
|
|
|
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(aesEncryptedBytes.data()), reinterpret_cast<unsigned char*>(aesEncryptedBytes.data()), aesEncryptedBytes.size(), &aeskey, reinterpret_cast<unsigned char*>(aesivec.data()), AES_DECRYPT);
|
|
|
|
|
|
|
|
constexpr auto kDigestSize = 16;
|
|
|
|
auto dataSize = aesEncryptedBytes.size() - kDigestSize;
|
|
|
|
auto data = aesEncryptedBytes.subspan(0, dataSize);
|
|
|
|
auto hash = openssl::Sha256(data);
|
2018-03-27 12:16:00 +00:00
|
|
|
if (bytes::compare(gsl::make_span(hash).subspan(0, kDigestSize), aesEncryptedBytes.subspan(dataSize)) != 0) {
|
2017-06-26 17:38:16 +00:00
|
|
|
LOG(("Config Error: Bad digest."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
mtpBuffer buffer;
|
|
|
|
buffer.resize(data.size() / sizeof(mtpPrime));
|
2018-03-27 12:16:00 +00:00
|
|
|
bytes::copy(bytes::make_span(buffer), data);
|
2017-06-26 17:38:16 +00:00
|
|
|
auto from = &*buffer.cbegin();
|
|
|
|
auto end = from + buffer.size();
|
|
|
|
auto realLength = *from++;
|
|
|
|
if (realLength <= 0 || realLength > dataSize || (realLength & 0x03)) {
|
|
|
|
LOG(("Config Error: Bad length %1.").arg(realLength));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
_simpleConfig.read(from, end);
|
|
|
|
} catch (...) {
|
|
|
|
LOG(("Config Error: Could not read configSimple."));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ((end - from) * sizeof(mtpPrime) != (dataSize - realLength)) {
|
|
|
|
LOG(("Config Error: Bad read length %1, should be %2.").arg((end - from) * sizeof(mtpPrime)).arg(dataSize - realLength));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpecialConfigRequest::handleResponse(const QByteArray &bytes) {
|
|
|
|
if (!decryptSimpleConfig(bytes)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(_simpleConfig.type() == mtpc_help_configSimple);
|
2017-06-26 17:38:16 +00:00
|
|
|
auto &config = _simpleConfig.c_help_configSimple();
|
|
|
|
auto now = unixtime();
|
|
|
|
if (now < config.vdate.v || now > config.vexpires.v) {
|
|
|
|
LOG(("Config Error: Bad date frame for simple config: %1-%2, our time is %3.").arg(config.vdate.v).arg(config.vexpires.v).arg(now));
|
|
|
|
return;
|
|
|
|
}
|
2018-05-02 19:27:03 +00:00
|
|
|
if (config.vrules.v.empty()) {
|
2017-06-26 17:38:16 +00:00
|
|
|
LOG(("Config Error: Empty simple config received."));
|
|
|
|
return;
|
|
|
|
}
|
2018-05-02 19:27:03 +00:00
|
|
|
for (auto &rule : config.vrules.v) {
|
|
|
|
Assert(rule.type() == mtpc_accessPointRule);
|
|
|
|
auto &data = rule.c_accessPointRule();
|
|
|
|
const auto phoneRules = qs(data.vphone_prefix_rules);
|
|
|
|
if (!CheckPhoneByPrefixesRules(_phone, phoneRules)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto dcId = data.vdc_id.v;
|
|
|
|
for (const auto &address : data.vips.v) {
|
|
|
|
const auto parseIp = [](const MTPint &ipv4) {
|
|
|
|
const auto ip = *reinterpret_cast<const uint32*>(&ipv4.v);
|
|
|
|
return qsl("%1.%2.%3.%4"
|
|
|
|
).arg((ip >> 24) & 0xFF
|
|
|
|
).arg((ip >> 16) & 0xFF
|
|
|
|
).arg((ip >> 8) & 0xFF
|
|
|
|
).arg(ip & 0xFF).toStdString();
|
|
|
|
};
|
|
|
|
switch (address.type()) {
|
|
|
|
case mtpc_ipPort: {
|
|
|
|
const auto &fields = address.c_ipPort();
|
|
|
|
_callback(dcId, parseIp(fields.vipv4), fields.vport.v, {});
|
|
|
|
} break;
|
|
|
|
case mtpc_ipPortSecret: {
|
|
|
|
const auto &fields = address.c_ipPortSecret();
|
|
|
|
_callback(
|
|
|
|
dcId,
|
|
|
|
parseIp(fields.vipv4),
|
|
|
|
fields.vport.v,
|
|
|
|
bytes::make_span(fields.vsecret.v));
|
|
|
|
} break;
|
|
|
|
default: Unexpected("Type in simpleConfig ips.");
|
|
|
|
}
|
|
|
|
}
|
2017-06-26 17:38:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-04 15:35:11 +00:00
|
|
|
DomainResolver::DomainResolver(Fn<void(
|
2018-05-17 19:58:00 +00:00
|
|
|
const QString &host,
|
|
|
|
const QStringList &ips,
|
2019-02-19 06:57:53 +00:00
|
|
|
crl::time expireAt)> callback)
|
2018-05-17 19:58:00 +00:00
|
|
|
: _callback(std::move(callback)) {
|
|
|
|
_manager.setProxy(QNetworkProxy::NoProxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::resolve(const QString &domain) {
|
|
|
|
resolve({ domain, false });
|
|
|
|
resolve({ domain, true });
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::resolve(const AttemptKey &key) {
|
|
|
|
if (_attempts.find(key) != end(_attempts)) {
|
|
|
|
return;
|
|
|
|
} else if (_requests.find(key) != end(_requests)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto i = _cache.find(key);
|
2019-02-19 06:57:53 +00:00
|
|
|
_lastTimestamp = crl::now();
|
2018-05-17 19:58:00 +00:00
|
|
|
if (i != end(_cache) && i->second.expireAt > _lastTimestamp) {
|
|
|
|
checkExpireAndPushResult(key.domain);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto hosts = DnsDomains();
|
|
|
|
std::random_device rd;
|
|
|
|
ranges::shuffle(hosts, std::mt19937(rd()));
|
|
|
|
_attempts.emplace(key, std::move(hosts));
|
|
|
|
sendNextRequest(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::checkExpireAndPushResult(const QString &domain) {
|
|
|
|
const auto ipv4 = _cache.find({ domain, false });
|
|
|
|
if (ipv4 == end(_cache) || ipv4->second.expireAt <= _lastTimestamp) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto result = ipv4->second;
|
|
|
|
const auto ipv6 = _cache.find({ domain, true });
|
|
|
|
if (ipv6 != end(_cache) && ipv6->second.expireAt > _lastTimestamp) {
|
|
|
|
result.ips.append(ipv6->second.ips);
|
|
|
|
accumulate_min(result.expireAt, ipv6->second.expireAt);
|
|
|
|
}
|
|
|
|
InvokeQueued(this, [=] {
|
|
|
|
_callback(domain, result.ips, result.expireAt);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::sendNextRequest(const AttemptKey &key) {
|
|
|
|
auto i = _attempts.find(key);
|
|
|
|
if (i == end(_attempts)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto &hosts = i->second;
|
|
|
|
const auto host = hosts.back();
|
|
|
|
hosts.pop_back();
|
|
|
|
|
|
|
|
if (!hosts.empty()) {
|
|
|
|
App::CallDelayed(kSendNextTimeout, this, [=] {
|
|
|
|
sendNextRequest(key);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
performRequest(key, host);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::performRequest(
|
|
|
|
const AttemptKey &key,
|
|
|
|
const QString &host) {
|
|
|
|
auto url = QUrl();
|
|
|
|
url.setScheme(qsl("https"));
|
|
|
|
url.setHost(host);
|
|
|
|
url.setPath(qsl("/resolve"));
|
|
|
|
url.setQuery(
|
|
|
|
qsl("name=%1&type=%2").arg(key.domain).arg(key.ipv6 ? 28 : 1));
|
|
|
|
auto request = QNetworkRequest();
|
|
|
|
request.setRawHeader("Host", "dns.google.com");
|
|
|
|
request.setUrl(url);
|
|
|
|
request.setRawHeader("User-Agent", kUserAgent);
|
|
|
|
const auto i = _requests.emplace(
|
|
|
|
key,
|
|
|
|
std::vector<ServiceWebRequest>()).first;
|
|
|
|
const auto reply = i->second.emplace_back(
|
|
|
|
_manager.get(request)
|
|
|
|
).reply;
|
|
|
|
connect(reply, &QNetworkReply::finished, this, [=] {
|
|
|
|
requestFinished(key, reply);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void DomainResolver::requestFinished(
|
|
|
|
const AttemptKey &key,
|
|
|
|
not_null<QNetworkReply*> reply) {
|
|
|
|
const auto result = finalizeRequest(key, reply);
|
|
|
|
const auto response = ParseDnsResponse(result);
|
|
|
|
if (response.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_requests.erase(key);
|
|
|
|
_attempts.erase(key);
|
|
|
|
|
|
|
|
auto entry = CacheEntry();
|
|
|
|
auto ttl = kMaxTimeToLive;
|
|
|
|
for (const auto &item : response) {
|
|
|
|
entry.ips.push_back(item.data);
|
|
|
|
accumulate_min(ttl, std::max(
|
2019-02-19 06:57:53 +00:00
|
|
|
item.TTL * crl::time(1000),
|
2018-05-17 19:58:00 +00:00
|
|
|
kMinTimeToLive));
|
|
|
|
}
|
2019-02-19 06:57:53 +00:00
|
|
|
_lastTimestamp = crl::now();
|
2018-05-17 19:58:00 +00:00
|
|
|
entry.expireAt = _lastTimestamp + ttl;
|
|
|
|
_cache[key] = std::move(entry);
|
|
|
|
|
|
|
|
checkExpireAndPushResult(key.domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray DomainResolver::finalizeRequest(
|
|
|
|
const AttemptKey &key,
|
|
|
|
not_null<QNetworkReply*> reply) {
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
2018-06-04 15:35:11 +00:00
|
|
|
LOG(("Resolve Error: Failed to get response, error: %2 (%3)"
|
2018-05-17 19:58:00 +00:00
|
|
|
).arg(reply->errorString()
|
|
|
|
).arg(reply->error()));
|
|
|
|
}
|
|
|
|
const auto result = reply->readAll();
|
|
|
|
const auto i = _requests.find(key);
|
|
|
|
if (i != end(_requests)) {
|
|
|
|
auto &requests = i->second;
|
|
|
|
const auto from = ranges::remove(
|
|
|
|
requests,
|
|
|
|
reply,
|
|
|
|
[](const ServiceWebRequest &request) { return request.reply; });
|
|
|
|
requests.erase(from, end(requests));
|
|
|
|
if (requests.empty()) {
|
|
|
|
_requests.erase(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-06-26 17:38:16 +00:00
|
|
|
} // namespace MTP
|