tst_QDnsLookup: add a test for setNameserver

I had to write a sample query test to ensure that those servers can be
reached. They can't from the my corporate network, for example:

QDEBUG : tst_QDnsLookup::setNameserver(normal) QHostAddress("8.8.8.8") discarded: "Network operation timed out"
QDEBUG : tst_QDnsLookup::setNameserver(normal) QHostAddress("2001:4860:4860::8888") discarded: "Network unreachable"
QDEBUG : tst_QDnsLookup::setNameserver(normal) QHostAddress("1.1.1.1") discarded: "Connection refused"
QDEBUG : tst_QDnsLookup::setNameserver(normal) QHostAddress("2606:4700:4700::1111") discarded: "Network unreachable"

This will also take care of ignoring the IPv6 servers on systems without
it (as above).

Change-Id: I3e3bfef633af4130a03afffd175de18af24add70
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Thiago Macieira 2023-05-10 13:17:02 -07:00
parent bce7009f55
commit 282b078fca
2 changed files with 156 additions and 0 deletions

View File

@ -11,3 +11,8 @@ qt_internal_add_test(tst_qdnslookup
LIBRARIES LIBRARIES
Qt::Network Qt::Network
) )
qt_internal_extend_target(tst_qdnslookup CONDITION WIN32
LIBRARIES
iphlpapi
)

View File

@ -13,6 +13,13 @@
#include <QtNetwork/QNetworkDatagram> #include <QtNetwork/QNetworkDatagram>
#include <QtNetwork/QUdpSocket> #include <QtNetwork/QUdpSocket>
#ifdef Q_OS_UNIX
# include <QtCore/QFile>
#else
# include <winsock2.h>
# include <iphlpapi.h>
#endif
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
static const int Timeout = 15000; // 15s static const int Timeout = 15000; // 15s
@ -40,9 +47,123 @@ private slots:
void lookupReuse(); void lookupReuse();
void lookupAbortRetry(); void lookupAbortRetry();
void setNameserverLoopback(); void setNameserverLoopback();
void setNameserver_data();
void setNameserver();
void bindingsAndProperties(); 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" // <root domain> IN SOA
;
static QList<QHostAddress> systemNameservers()
{
QList<QHostAddress> result;
#ifdef Q_OS_WIN
ULONG infosize = 0;
DWORD r = GetNetworkParams(nullptr, &infosize);
auto buffer = std::make_unique<uchar[]>(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<QHostAddress> 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<QHostAddress> 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<quint16>(ptr) == id
&& (ptr[2] & 0x80) // is a reply
&& (ptr[3] & 0xf) == 0 // rcode NOERROR
&& qFromBigEndian<quint16>(ptr + 4) == 1 // qdcount
&& qFromBigEndian<quint16>(ptr + 6) >= 1; // ancount
if (!ok) {
qDebug() << addr << "discarded: invalid reply";
continue;
}
result.emplaceBack(std::move(addr));
}
return result;
}
void tst_QDnsLookup::initTestCase() void tst_QDnsLookup::initTestCase()
{ {
if (qgetenv("QTEST_ENVIRONMENT") == "ci") if (qgetenv("QTEST_ENVIRONMENT") == "ci")
@ -412,6 +533,36 @@ void tst_QDnsLookup::setNameserverLoopback()
QCOMPARE(lookup.error(), QDnsLookup::NotFoundError); QCOMPARE(lookup.error(), QDnsLookup::NotFoundError);
} }
void tst_QDnsLookup::setNameserver_data()
{
static QList<QHostAddress> servers = systemNameservers() + globalPublicNameservers();
QTest::addColumn<QHostAddress>("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() void tst_QDnsLookup::bindingsAndProperties()
{ {
QDnsLookup lookup; QDnsLookup lookup;