diff --git a/tests/auto/network/kernel/qdnslookup/CMakeLists.txt b/tests/auto/network/kernel/qdnslookup/CMakeLists.txt index 6a74ab7dc91..b069ba3ddc1 100644 --- a/tests/auto/network/kernel/qdnslookup/CMakeLists.txt +++ b/tests/auto/network/kernel/qdnslookup/CMakeLists.txt @@ -11,3 +11,8 @@ qt_internal_add_test(tst_qdnslookup LIBRARIES Qt::Network ) + +qt_internal_extend_target(tst_qdnslookup CONDITION WIN32 + LIBRARIES + iphlpapi +) diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp index 568f0d84b86..4e2a5397a3c 100644 --- a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp +++ b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp @@ -13,6 +13,13 @@ #include #include +#ifdef Q_OS_UNIX +# include +#else +# include +# include +#endif + using namespace Qt::StringLiterals; static const int Timeout = 15000; // 15s @@ -40,9 +47,123 @@ private slots: void lookupReuse(); void lookupAbortRetry(); void setNameserverLoopback(); + void setNameserver_data(); + void setNameserver(); void bindingsAndProperties(); }; +static constexpr qsizetype HeaderSize = 6 * sizeof(quint16); +static const char preparedDnsQuery[] = + // header + "\x00\x00" // transaction ID, we'll replace + "\x01\x20" // flags + "\x00\x01" // qdcount + "\x00\x00" // ancount + "\x00\x00" // nscount + "\x00\x00" // arcount + // query: + "\x00\x00\x06\x00\x01" // IN SOA + ; + +static QList systemNameservers() +{ + QList result; + +#ifdef Q_OS_WIN + ULONG infosize = 0; + DWORD r = GetNetworkParams(nullptr, &infosize); + auto buffer = std::make_unique(infosize); + auto info = new (buffer.get()) FIXED_INFO; + r = GetNetworkParams(info, &infosize); + if (r == NO_ERROR) { + for (PIP_ADDR_STRING ptr = &info->DnsServerList; ptr; ptr = ptr->Next) { + QLatin1StringView addr(ptr->IpAddress.String); + result.emplaceBack(addr); + } + } +#else + QFile f("/etc/resolv.conf"); + if (!f.open(QIODevice::ReadOnly)) + return result; + + while (!f.atEnd()) { + static const char command[] = "nameserver"; + QByteArray line = f.readLine().simplified(); + if (!line.startsWith(command)) + continue; + + QString addr = QLatin1StringView(line).mid(sizeof(command)); + result.emplaceBack(addr); + } +#endif + + return result; +} + +static QList globalPublicNameservers() +{ + const char *const candidates[] = { + // Google's dns.google + "8.8.8.8", "2001:4860:4860::8888", + //"8.8.4.4", "2001:4860:4860::8844", + + // CloudFare's one.one.one.one + "1.1.1.1", "2606:4700:4700::1111", + //"1.0.0.1", "2606:4700:4700::1001", + + // Quad9's dns9 + //"9.9.9.9", "2620:fe::9", + }; + + QList result; + QRandomGenerator &rng = *QRandomGenerator::system(); + for (auto name : candidates) { + // check the candidates for reachability + QHostAddress addr{QLatin1StringView(name)}; + quint16 id = quint16(rng()); + QByteArray data(preparedDnsQuery, sizeof(preparedDnsQuery)); + char *ptr = data.data(); + qToBigEndian(id, ptr); + + QUdpSocket socket; + socket.connectToHost(addr, 53); + if (socket.waitForConnected(1)) + socket.write(data); + + if (!socket.waitForReadyRead(1000)) { + qDebug() << addr << "discarded:" << socket.errorString(); + continue; + } + + QNetworkDatagram dgram = socket.receiveDatagram(); + if (!dgram.isValid()) { + qDebug() << addr << "discarded:" << socket.errorString(); + continue; + } + + data = dgram.data(); + ptr = data.data(); + if (data.size() < HeaderSize) { + qDebug() << addr << "discarded: reply too small"; + continue; + } + + bool ok = qFromBigEndian(ptr) == id + && (ptr[2] & 0x80) // is a reply + && (ptr[3] & 0xf) == 0 // rcode NOERROR + && qFromBigEndian(ptr + 4) == 1 // qdcount + && qFromBigEndian(ptr + 6) >= 1; // ancount + if (!ok) { + qDebug() << addr << "discarded: invalid reply"; + continue; + } + + result.emplaceBack(std::move(addr)); + } + + return result; +} + void tst_QDnsLookup::initTestCase() { if (qgetenv("QTEST_ENVIRONMENT") == "ci") @@ -412,6 +533,36 @@ void tst_QDnsLookup::setNameserverLoopback() QCOMPARE(lookup.error(), QDnsLookup::NotFoundError); } +void tst_QDnsLookup::setNameserver_data() +{ + static QList servers = systemNameservers() + globalPublicNameservers(); + QTest::addColumn("server"); + + if (servers.isEmpty()) { + QSKIP("No reachable DNS servers were found"); + } else { + for (const QHostAddress &h : std::as_const(servers)) + QTest::addRow("%s", qUtf8Printable(h.toString())) << h; + } +} + +void tst_QDnsLookup::setNameserver() +{ + QFETCH(QHostAddress, server); + QDnsLookup lookup; + lookup.setNameserver(server); + + lookup.setType(QDnsLookup::Type::A); + lookup.setName(domainName("a-single")); + lookup.lookup(); + + QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); + QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError)); + QVERIFY(!lookup.hostAddressRecords().isEmpty()); + QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single")); + QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1")); +} + void tst_QDnsLookup::bindingsAndProperties() { QDnsLookup lookup;