1/****************************************************************************
2**
3** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
4** Copyright (C) 2016 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the test suite of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30
31#include <QtTest/QtTest>
32#include <QtNetwork/QDnsLookup>
33#include <QtNetwork/QHostAddress>
34
35static const int Timeout = 15000; // 15s
36
37class tst_QDnsLookup: public QObject
38{
39 Q_OBJECT
40
41 QString domainName(const QString &input);
42 QString domainNameList(const QString &input);
43 QStringList domainNameListAlternatives(const QString &input);
44public slots:
45 void initTestCase();
46
47private slots:
48 void lookup_data();
49 void lookup();
50 void lookupReuse();
51 void lookupAbortRetry();
52};
53
54void tst_QDnsLookup::initTestCase()
55{
56 QTest::addColumn<QString>(name: "tld");
57 QTest::newRow(dataTag: "normal") << ".test.qt-project.org";
58 QTest::newRow(dataTag: "idn") << ".alqualond\xc3\xab.test.qt-project.org";
59}
60
61QString tst_QDnsLookup::domainName(const QString &input)
62{
63 if (input.isEmpty())
64 return input;
65
66 if (input.endsWith(c: QLatin1Char('.'))) {
67 QString nodot = input;
68 nodot.chop(n: 1);
69 return nodot;
70 }
71
72 QFETCH_GLOBAL(QString, tld);
73 return input + tld;
74}
75
76QString tst_QDnsLookup::domainNameList(const QString &input)
77{
78 QStringList list = input.split(sep: QLatin1Char(';'));
79 QString result;
80 foreach (const QString &s, list) {
81 if (!result.isEmpty())
82 result += ';';
83 result += domainName(input: s);
84 }
85 return result;
86}
87
88QStringList tst_QDnsLookup::domainNameListAlternatives(const QString &input)
89{
90 QStringList alternatives = input.split(sep: '|');
91 for (int i = 0; i < alternatives.length(); ++i)
92 alternatives[i] = domainNameList(input: alternatives[i]);
93 return alternatives;
94}
95
96void tst_QDnsLookup::lookup_data()
97{
98 QTest::addColumn<int>(name: "type");
99 QTest::addColumn<QString>(name: "domain");
100 QTest::addColumn<int>(name: "error");
101 QTest::addColumn<QString>(name: "cname");
102 QTest::addColumn<QString>(name: "host");
103 QTest::addColumn<QString>(name: "mx");
104 QTest::addColumn<QString>(name: "ns");
105 QTest::addColumn<QString>(name: "ptr");
106 QTest::addColumn<QString>(name: "srv");
107 QTest::addColumn<QString>(name: "txt");
108
109 QTest::newRow(dataTag: "a-empty") << int(QDnsLookup::A) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << ""<< "" << "";
110 QTest::newRow(dataTag: "a-notfound") << int(QDnsLookup::A) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
111 QTest::newRow(dataTag: "a-single") << int(QDnsLookup::A) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "";
112 QTest::newRow(dataTag: "a-multi") << int(QDnsLookup::A) << "a-multi" << int(QDnsLookup::NoError) << "" << "192.0.2.1;192.0.2.2;192.0.2.3" << "" << "" << "" << "" << "";
113 QTest::newRow(dataTag: "aaaa-empty") << int(QDnsLookup::AAAA) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
114 QTest::newRow(dataTag: "aaaa-notfound") << int(QDnsLookup::AAAA) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
115 QTest::newRow(dataTag: "aaaa-single") << int(QDnsLookup::AAAA) << "aaaa-single" << int(QDnsLookup::NoError) << "" << "2001:db8::1" << "" << "" << "" << "" << "";
116 QTest::newRow(dataTag: "aaaa-multi") << int(QDnsLookup::AAAA) << "aaaa-multi" << int(QDnsLookup::NoError) << "" << "2001:db8::1;2001:db8::2;2001:db8::3" << "" << "" << "" << "" << "";
117
118 QTest::newRow(dataTag: "any-empty") << int(QDnsLookup::ANY) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
119 QTest::newRow(dataTag: "any-notfound") << int(QDnsLookup::ANY) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
120 QTest::newRow(dataTag: "any-a-single") << int(QDnsLookup::ANY) << "a-single" << int(QDnsLookup::NoError) << "" << "192.0.2.1" << "" << "" << "" << "" << "";
121 QTest::newRow(dataTag: "any-a-plus-aaaa") << int(QDnsLookup::ANY) << "a-plus-aaaa" << int(QDnsLookup::NoError) << "" << "198.51.100.1;2001:db8::1:1" << "" << "" << "" << "" << "";
122 QTest::newRow(dataTag: "any-multi") << int(QDnsLookup::ANY) << "multi" << int(QDnsLookup::NoError) << "" << "198.51.100.1;198.51.100.2;198.51.100.3;2001:db8::1:1;2001:db8::1:2" << "" << "" << "" << "" << "";
123
124 QTest::newRow(dataTag: "mx-empty") << int(QDnsLookup::MX) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
125 QTest::newRow(dataTag: "mx-notfound") << int(QDnsLookup::MX) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
126 QTest::newRow(dataTag: "mx-single") << int(QDnsLookup::MX) << "mx-single" << int(QDnsLookup::NoError) << "" << "" << "10 multi" << "" << "" << "" << "";
127 QTest::newRow(dataTag: "mx-single-cname") << int(QDnsLookup::MX) << "mx-single-cname" << int(QDnsLookup::NoError) << "" << "" << "10 cname" << "" << "" << "" << "";
128 QTest::newRow(dataTag: "mx-multi") << int(QDnsLookup::MX) << "mx-multi" << int(QDnsLookup::NoError) << "" << "" << "10 multi;20 a-single" << "" << "" << "" << "";
129 QTest::newRow(dataTag: "mx-multi-sameprio") << int(QDnsLookup::MX) << "mx-multi-sameprio" << int(QDnsLookup::NoError) << "" << ""
130 << "10 multi;10 a-single|"
131 "10 a-single;10 multi" << "" << "" << "" << "";
132
133 QTest::newRow(dataTag: "ns-empty") << int(QDnsLookup::NS) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
134 QTest::newRow(dataTag: "ns-notfound") << int(QDnsLookup::NS) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
135 QTest::newRow(dataTag: "ns-single") << int(QDnsLookup::NS) << "ns-single" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net." << "" << "" << "";
136 QTest::newRow(dataTag: "ns-multi") << int(QDnsLookup::NS) << "ns-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "ns11.cloudns.net.;ns12.cloudns.net." << "" << "" << "";
137
138 QTest::newRow(dataTag: "ptr-empty") << int(QDnsLookup::PTR) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
139 QTest::newRow(dataTag: "ptr-notfound") << int(QDnsLookup::PTR) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
140#if 0
141 // temporarily disabled since the new hosting provider can't insert
142 // PTR records outside of the in-addr.arpa zone
143 QTest::newRow("ptr-single") << int(QDnsLookup::PTR) << "ptr-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "a-single" << "" << "";
144#endif
145
146 QTest::newRow(dataTag: "srv-empty") << int(QDnsLookup::SRV) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
147 QTest::newRow(dataTag: "srv-notfound") << int(QDnsLookup::SRV) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
148 QTest::newRow(dataTag: "srv-single") << int(QDnsLookup::SRV) << "_echo._tcp.srv-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "5 0 7 multi" << "";
149 QTest::newRow(dataTag: "srv-prio") << int(QDnsLookup::SRV) << "_echo._tcp.srv-prio" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "1 0 7 multi;2 0 7 a-plus-aaaa" << "";
150 QTest::newRow(dataTag: "srv-weighted") << int(QDnsLookup::SRV) << "_echo._tcp.srv-weighted" << int(QDnsLookup::NoError) << "" << "" << "" << "" << ""
151 << "5 75 7 multi;5 25 7 a-plus-aaaa|"
152 "5 25 7 a-plus-aaaa;5 75 7 multi" << "";
153 QTest::newRow(dataTag: "srv-multi") << int(QDnsLookup::SRV) << "_echo._tcp.srv-multi" << int(QDnsLookup::NoError) << "" << "" << "" << "" << ""
154 << "1 50 7 multi;2 50 7 a-single;2 50 7 aaaa-single;3 50 7 a-multi|"
155 "1 50 7 multi;2 50 7 aaaa-single;2 50 7 a-single;3 50 7 a-multi" << "";
156
157 QTest::newRow(dataTag: "txt-empty") << int(QDnsLookup::TXT) << "" << int(QDnsLookup::InvalidRequestError) << "" << "" << "" << "" << "" << "" << "";
158 QTest::newRow(dataTag: "txt-notfound") << int(QDnsLookup::TXT) << "invalid.invalid" << int(QDnsLookup::NotFoundError) << "" << "" << "" << "" << "" << "" << "";
159 QTest::newRow(dataTag: "txt-single") << int(QDnsLookup::TXT) << "txt-single" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello";
160 QTest::newRow(dataTag: "txt-multi-onerr") << int(QDnsLookup::TXT) << "txt-multi-onerr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << ""
161 << QString::fromLatin1(str: "Hello\0World", size: sizeof("Hello\0World") - 1);
162 QTest::newRow(dataTag: "txt-multi-multirr") << int(QDnsLookup::TXT) << "txt-multi-multirr" << int(QDnsLookup::NoError) << "" << "" << "" << "" << "" << "" << "Hello;World";
163}
164
165static QByteArray msgDnsLookup(QDnsLookup::Error actualError,
166 int expectedError,
167 const QString &domain,
168 const QString &cname,
169 const QString &host,
170 const QString &srv,
171 const QString &mx,
172 const QString &ns,
173 const QString &ptr,
174 const QString &errorString)
175{
176 QString result;
177 QTextStream str(&result);
178 str << "Actual error: " << actualError;
179 if (!errorString.isEmpty())
180 str << " (" << errorString << ')';
181 str << ", expected: " << expectedError;
182 str << ", domain: " << domain;
183 if (!cname.isEmpty())
184 str << ", cname: " << cname;
185 str << ", host: " << host;
186 if (!srv.isEmpty())
187 str << " server: " << srv;
188 if (!mx.isEmpty())
189 str << " mx: " << mx;
190 if (!ns.isEmpty())
191 str << " ns: " << ns;
192 if (!ptr.isEmpty())
193 str << " ptr: " << ptr;
194 return result.toLocal8Bit();
195}
196
197void tst_QDnsLookup::lookup()
198{
199 QFETCH(int, type);
200 QFETCH(QString, domain);
201 QFETCH(int, error);
202 QFETCH(QString, cname);
203 QFETCH(QString, host);
204 QFETCH(QString, mx);
205 QFETCH(QString, ns);
206 QFETCH(QString, ptr);
207 QFETCH(QString, srv);
208 QFETCH(QString, txt);
209
210 // transform the inputs
211 domain = domainName(input: domain);
212 cname = domainName(input: cname);
213 ns = domainNameList(input: ns);
214 ptr = domainNameList(input: ptr);
215
216 // SRV and MX have reply entries that can change order
217 // and we can't sort
218 QStringList mx_alternatives = domainNameListAlternatives(input: mx);
219 QStringList srv_alternatives = domainNameListAlternatives(input: srv);
220
221 QDnsLookup lookup;
222 lookup.setType(static_cast<QDnsLookup::Type>(type));
223 lookup.setName(domain);
224 lookup.lookup();
225 QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
226
227#if defined(Q_OS_ANDROID)
228 if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
229 QEXPECT_FAIL("", "Not yet supported on Android", Abort);
230#endif
231
232 QVERIFY2(int(lookup.error()) == error,
233 msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString()));
234 if (error == QDnsLookup::NoError)
235 QVERIFY(lookup.errorString().isEmpty());
236 QCOMPARE(int(lookup.type()), type);
237 QCOMPARE(lookup.name(), domain);
238
239 // canonical names
240 if (!cname.isEmpty()) {
241 QVERIFY(!lookup.canonicalNameRecords().isEmpty());
242 const QDnsDomainNameRecord cnameRecord = lookup.canonicalNameRecords().first();
243 QCOMPARE(cnameRecord.name(), domain);
244 QCOMPARE(cnameRecord.value(), cname);
245 } else {
246 QVERIFY(lookup.canonicalNameRecords().isEmpty());
247 }
248
249 // host addresses
250 const QString hostName = cname.isEmpty() ? domain : cname;
251 QStringList addresses;
252 foreach (const QDnsHostAddressRecord &record, lookup.hostAddressRecords()) {
253 //reply may include A & AAAA records for nameservers, ignore them and only look at records matching the query
254 if (record.name() == hostName)
255 addresses << record.value().toString().toLower();
256 }
257 addresses.sort();
258 QCOMPARE(addresses.join(';'), host);
259
260 // mail exchanges
261 QStringList mailExchanges;
262 foreach (const QDnsMailExchangeRecord &record, lookup.mailExchangeRecords()) {
263 QCOMPARE(record.name(), domain);
264 mailExchanges << QString::number(record.preference()) + QLatin1Char(' ') + record.exchange();
265 }
266 QVERIFY2(mx_alternatives.contains(mailExchanges.join(';')),
267 qPrintable("Actual: " + mailExchanges.join(';') + "\nExpected one of:\n" + mx_alternatives.join('\n')));
268
269 // name servers
270 QStringList nameServers;
271 foreach (const QDnsDomainNameRecord &record, lookup.nameServerRecords()) {
272 //reply may include NS records for authoritative nameservers, ignore them and only look at records matching the query
273 if (record.name() == domain)
274 nameServers << record.value();
275 }
276 nameServers.sort();
277 QCOMPARE(nameServers.join(';'), ns);
278
279 // pointers
280 if (!ptr.isEmpty()) {
281 QVERIFY(!lookup.pointerRecords().isEmpty());
282 const QDnsDomainNameRecord ptrRecord = lookup.pointerRecords().first();
283 QCOMPARE(ptrRecord.name(), domain);
284 QCOMPARE(ptrRecord.value(), ptr);
285 } else {
286 QVERIFY(lookup.pointerRecords().isEmpty());
287 }
288
289 // services
290 QStringList services;
291 foreach (const QDnsServiceRecord &record, lookup.serviceRecords()) {
292 QCOMPARE(record.name(), domain);
293 services << (QString::number(record.priority()) + QLatin1Char(' ')
294 + QString::number(record.weight()) + QLatin1Char(' ')
295 + QString::number(record.port()) + QLatin1Char(' ') + record.target());
296 }
297 QVERIFY2(srv_alternatives.contains(services.join(';')),
298 qPrintable("Actual: " + services.join(';') + "\nExpected one of:\n" + srv_alternatives.join('\n')));
299
300 // text
301 QStringList texts;
302 foreach (const QDnsTextRecord &record, lookup.textRecords()) {
303 QCOMPARE(record.name(), domain);
304 QString text;
305 foreach (const QByteArray &ba, record.values()) {
306 if (!text.isEmpty())
307 text += '\0';
308 text += QString::fromLatin1(str: ba);
309 }
310 texts << text;
311 }
312 texts.sort();
313 QCOMPARE(texts.join(';'), txt);
314}
315
316void tst_QDnsLookup::lookupReuse()
317{
318 QDnsLookup lookup;
319
320 // first lookup
321 lookup.setType(QDnsLookup::A);
322 lookup.setName(domainName(input: "a-single"));
323 lookup.lookup();
324 QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
325
326#if defined(Q_OS_ANDROID)
327 if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
328 QEXPECT_FAIL("", "Not yet supported on Android", Abort);
329#endif
330
331 QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
332 QVERIFY(!lookup.hostAddressRecords().isEmpty());
333 QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single"));
334 QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("192.0.2.1"));
335
336 // second lookup
337 lookup.setType(QDnsLookup::AAAA);
338 lookup.setName(domainName(input: "aaaa-single"));
339 lookup.lookup();
340 QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
341 QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
342 QVERIFY(!lookup.hostAddressRecords().isEmpty());
343 QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));
344 QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1"));
345}
346
347
348void tst_QDnsLookup::lookupAbortRetry()
349{
350 QDnsLookup lookup;
351
352 // try and abort the lookup
353 lookup.setType(QDnsLookup::A);
354 lookup.setName(domainName(input: "a-single"));
355 lookup.lookup();
356 lookup.abort();
357 QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
358 QCOMPARE(int(lookup.error()), int(QDnsLookup::OperationCancelledError));
359 QVERIFY(lookup.hostAddressRecords().isEmpty());
360
361 // retry a different lookup
362 lookup.setType(QDnsLookup::AAAA);
363 lookup.setName(domainName(input: "aaaa-single"));
364 lookup.lookup();
365 QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
366
367#if defined(Q_OS_ANDROID)
368 if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
369 QEXPECT_FAIL("", "Not yet supported on Android", Abort);
370#endif
371
372 QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
373 QVERIFY(!lookup.hostAddressRecords().isEmpty());
374 QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));
375 QCOMPARE(lookup.hostAddressRecords().first().value(), QHostAddress("2001:db8::1"));
376}
377
378QTEST_MAIN(tst_QDnsLookup)
379#include "tst_qdnslookup.moc"
380

source code of qtbase/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp