mtproto_domain_resolver.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "mtproto/details/mtproto_domain_resolver.h"
  8. #include "base/random.h"
  9. #include "base/invoke_queued.h"
  10. #include "base/call_delayed.h"
  11. #include <QtCore/QJsonDocument>
  12. #include <QtCore/QJsonArray>
  13. #include <QtCore/QJsonObject>
  14. #include <range/v3/algorithm/shuffle.hpp>
  15. #include <range/v3/algorithm/reverse.hpp>
  16. #include <range/v3/algorithm/remove.hpp>
  17. #include <random>
  18. namespace MTP::details {
  19. namespace {
  20. constexpr auto kSendNextTimeout = crl::time(800);
  21. constexpr auto kMinTimeToLive = 10 * crl::time(1000);
  22. constexpr auto kMaxTimeToLive = 300 * crl::time(1000);
  23. } // namespace
  24. const std::vector<QString> &DnsDomains() {
  25. static const auto kResult = std::vector<QString>{
  26. "google.com",
  27. "www.google.com",
  28. "google.ru",
  29. "www.google.ru",
  30. };
  31. return kResult;
  32. }
  33. QString GenerateDnsRandomPadding() {
  34. constexpr char kValid[] = "abcdefghijklmnopqrstuvwxyz"
  35. "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  36. auto result = QString();
  37. const auto count = [&] {
  38. constexpr auto kMinPadding = 13;
  39. constexpr auto kMaxPadding = 128;
  40. while (true) {
  41. const auto result = 1 + (base::RandomValue<uchar>() / 2);
  42. Assert(result <= kMaxPadding);
  43. if (result >= kMinPadding) {
  44. return result;
  45. }
  46. }
  47. }();
  48. result.resize(count);
  49. for (auto &ch : result) {
  50. ch = kValid[base::RandomValue<uchar>() % (sizeof(kValid) - 1)];
  51. }
  52. return result;
  53. }
  54. QByteArray DnsUserAgent() {
  55. static const auto kResult = QByteArray(
  56. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
  57. "AppleWebKit/537.36 (KHTML, like Gecko) "
  58. "Chrome/133.0.0.0 Safari/537.36");
  59. return kResult;
  60. }
  61. std::vector<DnsEntry> ParseDnsResponse(
  62. const QByteArray &bytes,
  63. std::optional<int> typeRestriction) {
  64. if (bytes.isEmpty()) {
  65. return {};
  66. }
  67. // Read and store to "result" all the data bytes from the response:
  68. // { ..,
  69. // "Answer": [
  70. // { .., "data": "bytes1", "TTL": int, .. },
  71. // { .., "data": "bytes2", "TTL": int, .. }
  72. // ],
  73. // .. }
  74. auto error = QJsonParseError{ 0, QJsonParseError::NoError };
  75. const auto document = QJsonDocument::fromJson(bytes, &error);
  76. if (error.error != QJsonParseError::NoError) {
  77. LOG(("Config Error: Failed to parse dns response JSON, error: %1"
  78. ).arg(error.errorString()));
  79. return {};
  80. } else if (!document.isObject()) {
  81. LOG(("Config Error: Not an object received in dns response JSON."));
  82. return {};
  83. }
  84. const auto response = document.object();
  85. const auto answerIt = response.find("Answer");
  86. if (answerIt == response.constEnd()) {
  87. LOG(("Config Error: Could not find Answer in dns response JSON."));
  88. return {};
  89. } else if (!(*answerIt).isArray()) {
  90. LOG(("Config Error: Not an array received "
  91. "in Answer in dns response JSON."));
  92. return {};
  93. }
  94. const auto array = (*answerIt).toArray();
  95. auto result = std::vector<DnsEntry>();
  96. for (const auto elem : array) {
  97. if (!elem.isObject()) {
  98. LOG(("Config Error: Not an object found "
  99. "in Answer array in dns response JSON."));
  100. continue;
  101. }
  102. const auto object = elem.toObject();
  103. if (typeRestriction) {
  104. const auto typeIt = object.find("type");
  105. const auto type = int(base::SafeRound((*typeIt).toDouble()));
  106. if (!(*typeIt).isDouble()) {
  107. LOG(("Config Error: Not a number in type field "
  108. "in Answer array in dns response JSON."));
  109. continue;
  110. } else if (type != *typeRestriction) {
  111. continue;
  112. }
  113. }
  114. const auto dataIt = object.find("data");
  115. if (dataIt == object.constEnd()) {
  116. LOG(("Config Error: Could not find data "
  117. "in Answer array entry in dns response JSON."));
  118. continue;
  119. } else if (!(*dataIt).isString()) {
  120. LOG(("Config Error: Not a string data found "
  121. "in Answer array entry in dns response JSON."));
  122. continue;
  123. }
  124. const auto ttlIt = object.find("TTL");
  125. const auto ttl = (ttlIt != object.constEnd())
  126. ? crl::time(base::SafeRound((*ttlIt).toDouble()))
  127. : crl::time(0);
  128. result.push_back({ (*dataIt).toString(), ttl });
  129. }
  130. return result;
  131. }
  132. ServiceWebRequest::ServiceWebRequest(not_null<QNetworkReply*> reply)
  133. : reply(reply.get()) {
  134. }
  135. ServiceWebRequest::ServiceWebRequest(ServiceWebRequest &&other)
  136. : reply(base::take(other.reply)) {
  137. }
  138. ServiceWebRequest &ServiceWebRequest::operator=(ServiceWebRequest &&other) {
  139. if (reply != other.reply) {
  140. destroy();
  141. reply = base::take(other.reply);
  142. }
  143. return *this;
  144. }
  145. void ServiceWebRequest::destroy() {
  146. if (const auto value = base::take(reply)) {
  147. value->disconnect(
  148. value,
  149. &QNetworkReply::finished,
  150. nullptr,
  151. nullptr);
  152. value->abort();
  153. value->deleteLater();
  154. }
  155. }
  156. ServiceWebRequest::~ServiceWebRequest() {
  157. if (reply) {
  158. reply->deleteLater();
  159. }
  160. }
  161. DomainResolver::DomainResolver(Fn<void(
  162. const QString &host,
  163. const QStringList &ips,
  164. crl::time expireAt)> callback)
  165. : _callback(std::move(callback)) {
  166. _manager.setProxy(QNetworkProxy::NoProxy);
  167. }
  168. void DomainResolver::resolve(const QString &domain) {
  169. resolve({ domain, false });
  170. resolve({ domain, true });
  171. }
  172. void DomainResolver::resolve(const AttemptKey &key) {
  173. if (_attempts.find(key) != end(_attempts)) {
  174. return;
  175. } else if (_requests.find(key) != end(_requests)) {
  176. return;
  177. }
  178. const auto i = _cache.find(key);
  179. _lastTimestamp = crl::now();
  180. if (i != end(_cache) && i->second.expireAt > _lastTimestamp) {
  181. checkExpireAndPushResult(key.domain);
  182. return;
  183. }
  184. auto attempts = std::vector<Attempt>();
  185. auto domains = DnsDomains();
  186. std::random_device rd;
  187. ranges::shuffle(domains, std::mt19937(rd()));
  188. const auto takeDomain = [&] {
  189. const auto result = domains.back();
  190. domains.pop_back();
  191. return result;
  192. };
  193. const auto shuffle = [&](int from, int till) {
  194. Expects(till > from);
  195. ranges::shuffle(
  196. begin(attempts) + from,
  197. begin(attempts) + till,
  198. std::mt19937(rd()));
  199. };
  200. attempts.push_back({ Type::Google, "dns.google.com" });
  201. attempts.push_back({ Type::Google, takeDomain(), "dns" });
  202. attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
  203. while (!domains.empty()) {
  204. attempts.push_back({ Type::Google, takeDomain(), "dns" });
  205. }
  206. shuffle(0, 2);
  207. ranges::reverse(attempts); // We go from last to first.
  208. _attempts.emplace(key, Attempts{ std::move(attempts) });
  209. sendNextRequest(key);
  210. }
  211. void DomainResolver::checkExpireAndPushResult(const QString &domain) {
  212. const auto ipv4 = _cache.find({ domain, false });
  213. if (ipv4 == end(_cache) || ipv4->second.expireAt <= _lastTimestamp) {
  214. return;
  215. }
  216. auto result = ipv4->second;
  217. const auto ipv6 = _cache.find({ domain, true });
  218. if (ipv6 != end(_cache) && ipv6->second.expireAt > _lastTimestamp) {
  219. result.ips.append(ipv6->second.ips);
  220. accumulate_min(result.expireAt, ipv6->second.expireAt);
  221. }
  222. InvokeQueued(this, [=] {
  223. _callback(domain, result.ips, result.expireAt);
  224. });
  225. }
  226. void DomainResolver::sendNextRequest(const AttemptKey &key) {
  227. auto i = _attempts.find(key);
  228. if (i == end(_attempts)) {
  229. return;
  230. }
  231. auto &attempts = i->second;
  232. auto &list = attempts.list;
  233. const auto attempt = list.back();
  234. list.pop_back();
  235. if (!list.empty()) {
  236. base::call_delayed(kSendNextTimeout, &attempts.guard, [=] {
  237. sendNextRequest(key);
  238. });
  239. }
  240. performRequest(key, attempt);
  241. }
  242. void DomainResolver::performRequest(
  243. const AttemptKey &key,
  244. const Attempt &attempt) {
  245. auto url = QUrl();
  246. url.setScheme("https");
  247. auto request = QNetworkRequest();
  248. switch (attempt.type) {
  249. case Type::Mozilla: {
  250. url.setHost(attempt.data);
  251. url.setPath("/dns-query");
  252. url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
  253. ).arg(key.domain
  254. ).arg(key.ipv6 ? 28 : 1
  255. ).arg(GenerateDnsRandomPadding()));
  256. request.setRawHeader("accept", "application/dns-json");
  257. } break;
  258. case Type::Google: {
  259. url.setHost(attempt.data);
  260. url.setPath("/resolve");
  261. url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
  262. ).arg(key.domain
  263. ).arg(key.ipv6 ? 28 : 1
  264. ).arg(GenerateDnsRandomPadding()));
  265. if (!attempt.host.isEmpty()) {
  266. const auto host = attempt.host + ".google.com";
  267. request.setRawHeader("Host", host.toLatin1());
  268. }
  269. } break;
  270. default: Unexpected("Type in DomainResolver::performRequest.");
  271. }
  272. request.setUrl(url);
  273. request.setRawHeader("User-Agent", DnsUserAgent());
  274. const auto i = _requests.emplace(
  275. key,
  276. std::vector<ServiceWebRequest>()).first;
  277. const auto reply = i->second.emplace_back(
  278. _manager.get(request)
  279. ).reply;
  280. connect(reply, &QNetworkReply::finished, this, [=] {
  281. requestFinished(key, reply);
  282. });
  283. }
  284. void DomainResolver::requestFinished(
  285. const AttemptKey &key,
  286. not_null<QNetworkReply*> reply) {
  287. const auto result = finalizeRequest(key, reply);
  288. const auto response = ParseDnsResponse(result);
  289. if (response.empty()) {
  290. return;
  291. }
  292. _requests.erase(key);
  293. _attempts.erase(key);
  294. auto entry = CacheEntry();
  295. auto ttl = kMaxTimeToLive;
  296. for (const auto &item : response) {
  297. entry.ips.push_back(item.data);
  298. ttl = std::min(
  299. ttl,
  300. std::max(item.TTL * crl::time(1000), kMinTimeToLive));
  301. }
  302. _lastTimestamp = crl::now();
  303. entry.expireAt = _lastTimestamp + ttl;
  304. _cache[key] = std::move(entry);
  305. checkExpireAndPushResult(key.domain);
  306. }
  307. QByteArray DomainResolver::finalizeRequest(
  308. const AttemptKey &key,
  309. not_null<QNetworkReply*> reply) {
  310. if (reply->error() != QNetworkReply::NoError) {
  311. DEBUG_LOG(("Resolve Error: Failed to get response, error: %2 (%3)"
  312. ).arg(reply->errorString()
  313. ).arg(reply->error()));
  314. }
  315. const auto result = reply->readAll();
  316. const auto i = _requests.find(key);
  317. if (i != end(_requests)) {
  318. auto &requests = i->second;
  319. const auto from = ranges::remove(
  320. requests,
  321. reply,
  322. [](const ServiceWebRequest &request) { return request.reply; });
  323. requests.erase(from, end(requests));
  324. if (requests.empty()) {
  325. _requests.erase(i);
  326. }
  327. }
  328. return result;
  329. }
  330. } // namespace MTP::details