Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qdnslookup_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
2// Copyright (C) 2023 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qdnslookup_p.h"
6
7#include <qendian.h>
8#include <qscopedpointer.h>
9#include <qspan.h>
10#include <qurl.h>
11#include <qvarlengtharray.h>
12#include <private/qnativesocketengine_p.h> // for setSockAddr
13#include <private/qtnetwork-config_p.h>
14
16
17#include <sys/types.h>
18#include <netinet/in.h>
19#include <arpa/nameser.h>
20#if __has_include(<arpa/nameser_compat.h>)
21# include <arpa/nameser_compat.h>
22#endif
23#include <errno.h>
24#include <resolv.h>
25
26#include <array>
27
28#ifndef T_OPT
29// the older arpa/nameser_compat.h wasn't updated between 1999 and 2016 in glibc
30# define T_OPT ns_t_opt
31#endif
32
34
35using namespace Qt::StringLiterals;
37
38// https://www.rfc-editor.org/rfc/rfc6891
39static constexpr unsigned char Edns0Record[] = {
40 0x00, // root label
41 T_OPT >> 8, T_OPT & 0xff, // type OPT
43 NOERROR, // extended rcode
44 0, // version
45 0x00, 0x00, // flags
46 0x00, 0x00, // option length
47};
48
49// maximum length of a EDNS0 query with a 255-character domain (rounded up to 16)
50static constexpr qsizetype QueryBufferSize =
51 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 + sizeof(Edns0Record);
52using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>;
53
54namespace {
55struct QDnsCachedName
56{
58 int code = 0;
59 QDnsCachedName(const QString &name, int code) : name(name), code(code) {}
60};
61}
63using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
64
65#if QT_CONFIG(res_setservers)
66// https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html
67// https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html
68static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
69{
70 union res_sockaddr_union u;
71 setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
72 res_setservers(state, &u, 1);
73 return true;
74}
75#else
76template <typename T> void setNsMap(T &ext, std::enable_if_t<sizeof(T::nsmap) != 0, uint16_t> v)
77{
78 // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
79 // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
80 // Unneeded since glibc 2.22 (2015), but doesn't hurt to set it
81 // See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7
82 ext.nsmap[0] = v;
83}
84template <typename T> void setNsMap(T &, ...)
85{
86 // fallback
87}
88
89template <bool Condition>
90using EnableIfIPv6 = std::enable_if_t<Condition, const QHostAddress *>;
91
92template <typename State>
94 EnableIfIPv6<sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
96{
97 // glibc-like API to set IPv6 name servers
98 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
99
100 // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
101 if (!ns) {
102 // Memory allocated here will be free()'d in res_close() as we
103 // have done res_init() above.
104 ns = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
106 state->_u._ext.nsaddrs[0] = ns;
107 }
108
109 setNsMap(state->_u._ext, MAXNS + 1);
110 state->_u._ext.nscount6 = 1;
111 setSockaddr(ns, *addr, port);
112 return true;
113}
114
115template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
116{
117 // fallback
118 return false;
119}
120
121static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
122{
123 state->nscount = 1;
124 state->nsaddr_list[0].sin_family = AF_UNSPEC;
125 if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
126 return setIpv6NameServer(state, &nameserver, port);
127 setSockaddr(&state->nsaddr_list[0], nameserver, port);
128 return true;
129}
130#endif // !QT_CONFIG(res_setservers)
131
132static int
133prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
134{
135 // Create header and our query
136 int queryLength = res_nmkquery(state, QUERY, label, C_IN, type, nullptr, 0, nullptr,
137 buffer.data(), buffer.size());
138 Q_ASSERT(queryLength < int(buffer.size()));
139 if (Q_UNLIKELY(queryLength < 0))
140 return queryLength;
141
142 // Append EDNS0 record and set the number of additional RRs to 1
143 Q_ASSERT(queryLength + sizeof(Edns0Record) < buffer.size());
144 std::copy_n(std::begin(Edns0Record), sizeof(Edns0Record), buffer.begin() + queryLength);
145 reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
146
147 return queryLength + sizeof(Edns0Record);
148}
149
150static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer,
151 ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
152{
153 // Check if a nameserver was set. If so, use it.
154 if (!nameserver.isNull()) {
155 if (!applyNameServer(state, nameserver, port)) {
157 QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
158 return -1;
159 }
160
161 // Request the name server attempt to authenticate the reply.
162 reinterpret_cast<HEADER *>(buffer.data())->ad = true;
163
164#ifdef RES_TRUSTAD
165 // Need to set this option even though we set the AD bit, otherwise
166 // glibc turns it off.
167 state->options |= RES_TRUSTAD;
168#endif
169 }
170
171 auto attemptToSend = [&]() {
172 std::memset(buffer.data(), 0, HFIXEDSZ); // the header is enough
173 int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
174 if (responseLength >= 0)
175 return responseLength; // success
176
177 // libresolv uses ETIMEDOUT for resolver errors ("no answer")
178 if (errno == ECONNREFUSED)
180 else if (errno != ETIMEDOUT)
181 reply->makeResolverSystemError(); // some other error
182
183 auto query = reinterpret_cast<HEADER *>(qbuffer.data());
184 auto header = reinterpret_cast<HEADER *>(buffer.data());
185 if (query->id == header->id && header->qr)
186 reply->makeDnsRcodeError(header->rcode);
187 else
188 reply->makeTimeoutError(); // must really be a timeout
189 return -1;
190 };
191
192 // strictly use UDP, we'll deal with truncated replies ourselves
193 state->options |= RES_IGNTC;
194 int responseLength = attemptToSend();
195 if (responseLength < 0)
196 return responseLength;
197
198 // check if we need to use the virtual circuit (TCP)
199 auto header = reinterpret_cast<HEADER *>(buffer.data());
200 if (header->rcode == NOERROR && header->tc) {
201 // yes, increase our buffer size
202 buffer.resize(std::numeric_limits<quint16>::max());
203 header = reinterpret_cast<HEADER *>(buffer.data());
204
205 // remove the EDNS record in the query
206 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
207 qbuffer = qbuffer.first(qbuffer.size() - sizeof(Edns0Record));
208
209 // send using the virtual circuit
210 state->options |= RES_USEVC;
211 responseLength = attemptToSend();
212 if (Q_UNLIKELY(responseLength > buffer.size())) {
213 // Ok, we give up.
214 reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large"));
215 return -1;
216 }
217 }
218
219 // We only trust the AD bit in the reply if we're querying a custom name
220 // server or if we can tell the system administrator configured the resolver
221 // to trust replies.
222#ifndef RES_TRUSTAD
223 if (nameserver.isNull())
224 header->ad = false;
225#endif
226 reply->authenticData = header->ad;
227
228 return responseLength;
229}
230
231void QDnsLookupRunnable::query(QDnsLookupReply *reply)
232{
233 // Initialize state.
234 std::remove_pointer_t<res_state> state = {};
235 if (res_ninit(&state) < 0) {
236 int error = errno;
237 qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
238 return reply->makeResolverSystemError(error);
239 }
240 auto guard = qScopeGuard([&] { res_nclose(&state); });
241
242#ifdef QDNSLOOKUP_DEBUG
243 state.options |= RES_DEBUG;
244#endif
245
246 // Prepare the DNS query.
247 QueryBuffer qbuffer;
248 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
249 if (Q_UNLIKELY(queryLength < 0))
250 return reply->makeResolverSystemError();
251
252 // Perform DNS query.
254 int responseLength = -1;
255 switch (protocol) {
257 responseLength = sendStandardDns(reply, &state, qbuffer, buffer, nameserver, port);
258 break;
260 if (!sendDnsOverTls(reply, qbuffer, buffer))
261 return;
262 responseLength = buffer.size();
263 break;
264 }
265
266 if (responseLength < 0)
267 return;
268
269 // Check the reply is valid.
270 if (responseLength < int(sizeof(HEADER)))
271 return reply->makeInvalidReplyError();
272
273 // Parse the reply.
274 auto header = reinterpret_cast<HEADER *>(buffer.data());
275 if (header->rcode)
276 return reply->makeDnsRcodeError(header->rcode);
277
278 qptrdiff offset = sizeof(HEADER);
279 unsigned char *response = buffer.data();
280 int status;
281
282 auto expandHost = [&, cache = Cache{}](qptrdiff offset) mutable {
283 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
284 // compressed name, see if we already have it cached
285 if (offset + 1 < responseLength) {
286 int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
287 auto it = std::find_if(cache.constBegin(), cache.constEnd(),
288 [id](const QDnsCachedName &n) { return n.code == id; });
289 if (it != cache.constEnd()) {
290 status = 2;
291 return it->name;
292 }
293 }
294 }
295
296 // uncached, expand it
297 char host[MAXCDNAME + 1];
298 status = dn_expand(response, response + responseLength, response + offset,
299 host, sizeof(host));
300 if (status >= 0)
301 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
302
303 // failed
304 reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
305 return QString();
306 };
307
308 if (ntohs(header->qdcount) == 1) {
309 // Skip the query host, type (2 bytes) and class (2 bytes).
310 expandHost(offset);
311 if (status < 0)
312 return;
313 if (offset + status + 4 > responseLength)
314 header->qdcount = 0xffff; // invalid reply below
315 else
316 offset += status + 4;
317 }
318 if (ntohs(header->qdcount) > 1)
319 return reply->makeInvalidReplyError();
320
321 // Extract results.
322 const int answerCount = ntohs(header->ancount);
323 int answerIndex = 0;
324 while ((offset < responseLength) && (answerIndex < answerCount)) {
325 const QString name = expandHost(offset);
326 if (status < 0)
327 return;
328
329 offset += status;
330 if (offset + RRFIXEDSZ > responseLength) {
331 // probably just a truncated reply, return what we have
332 return;
333 }
334 const quint16 type = qFromBigEndian<quint16>(response + offset);
335 const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
336 const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
337 const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
338 offset += RRFIXEDSZ;
339 if (offset + size > responseLength)
340 return; // truncated
341 if (rrclass != C_IN)
342 continue;
343
344 if (type == QDnsLookup::A) {
345 if (size != 4)
346 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv4 address record"));
347 const quint32 addr = qFromBigEndian<quint32>(response + offset);
349 record.d->name = name;
350 record.d->timeToLive = ttl;
351 record.d->value = QHostAddress(addr);
352 reply->hostAddressRecords.append(record);
353 } else if (type == QDnsLookup::AAAA) {
354 if (size != 16)
355 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv6 address record"));
357 record.d->name = name;
358 record.d->timeToLive = ttl;
359 record.d->value = QHostAddress(response + offset);
360 reply->hostAddressRecords.append(record);
361 } else if (type == QDnsLookup::CNAME) {
363 record.d->name = name;
364 record.d->timeToLive = ttl;
365 record.d->value = expandHost(offset);
366 if (status < 0)
367 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
368 reply->canonicalNameRecords.append(record);
369 } else if (type == QDnsLookup::NS) {
371 record.d->name = name;
372 record.d->timeToLive = ttl;
373 record.d->value = expandHost(offset);
374 if (status < 0)
375 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
376 reply->nameServerRecords.append(record);
377 } else if (type == QDnsLookup::PTR) {
379 record.d->name = name;
380 record.d->timeToLive = ttl;
381 record.d->value = expandHost(offset);
382 if (status < 0)
383 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
384 reply->pointerRecords.append(record);
385 } else if (type == QDnsLookup::MX) {
386 const quint16 preference = qFromBigEndian<quint16>(response + offset);
388 record.d->exchange = expandHost(offset + 2);
389 record.d->name = name;
390 record.d->preference = preference;
391 record.d->timeToLive = ttl;
392 if (status < 0)
393 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
394 reply->mailExchangeRecords.append(record);
395 } else if (type == QDnsLookup::SRV) {
396 if (size < 7)
397 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
398 const quint16 priority = qFromBigEndian<quint16>(response + offset);
399 const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
400 const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
402 record.d->name = name;
403 record.d->target = expandHost(offset + 6);
404 record.d->port = port;
405 record.d->priority = priority;
406 record.d->timeToLive = ttl;
407 record.d->weight = weight;
408 if (status < 0)
409 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
410 reply->serviceRecords.append(record);
411 } else if (type == QDnsLookup::TLSA) {
412 // https://datatracker.ietf.org/doc/html/rfc6698#section-2.1
413 if (size < 3)
414 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record"));
415
416 const quint8 usage = response[offset];
417 const quint8 selector = response[offset + 1];
418 const quint8 matchType = response[offset + 2];
419
421 record.d->name = name;
422 record.d->timeToLive = ttl;
425 record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
426 record.d->value.assign(response + offset + 3, response + offset + size);
427 reply->tlsAssociationRecords.append(std::move(record));
428 } else if (type == QDnsLookup::TXT) {
430 record.d->name = name;
431 record.d->timeToLive = ttl;
433 while (txt < offset + size) {
434 const unsigned char length = response[txt];
435 txt++;
436 if (txt + length > offset + size)
437 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid text record"));
438 record.d->values << QByteArrayView(response + txt, length).toByteArray();
439 txt += length;
440 }
441 reply->textRecords.append(record);
442 }
443 offset += size;
444 answerIndex++;
445 }
446}
447
[qjs-as-container]
static constexpr auto IPv6Protocol
QByteArray toByteArray() const
Definition qbytearray.h:796
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
The QDnsDomainNameRecord class stores information about a domain name record.
Definition qdnslookup.h:31
The QDnsHostAddressRecord class stores information about a host address record.
Definition qdnslookup.h:53
static constexpr qsizetype ReplyBufferSize
bool sendDnsOverTls(QDnsLookupReply *reply, QSpan< unsigned char > query, ReplyBuffer &response)
QVarLengthArray< unsigned char, ReplyBufferSize > ReplyBuffer
@ ServerRefusedError
Definition qdnslookup.h:240
The QDnsMailExchangeRecord class stores information about a DNS MX record.
Definition qdnslookup.h:75
The QDnsServiceRecord class stores information about a DNS SRV record.
Definition qdnslookup.h:98
The QDnsTextRecord class stores information about a DNS TXT record.
Definition qdnslookup.h:123
The QDnsTlsAssociationRecord class stores information about a DNS TLSA record.
Definition qdnslookup.h:145
CertificateUsage
This enumeration contains valid values for the certificate usage field of TLS Association queries.
Definition qdnslookup.h:148
Selector
This enumeration contains valid values for the selector field of TLS Association queries.
Definition qdnslookup.h:166
MatchingType
This enumeration contains valid values for the matching type field of TLS Association queries.
Definition qdnslookup.h:180
The QHostAddress class provides an IP address.
bool isNull() const
Returns true if this host address is not valid for any host or interface.
NetworkLayerProtocol protocol() const
Returns the network layer protocol of the host address.
void setError(NetworkError errorCode, const QString &errorString)
Sets the error condition to be errorCode.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static constexpr qsizetype PreallocatedSize
QCache< int, Employee > cache
[0]
QSet< QString >::iterator it
else opt state
[0]
void qErrnoWarning(const char *msg,...)
Combined button and popup list for selecting options.
#define Q_UNLIKELY(x)
DBusConnection const char DBusError * error
static QString header(const QString &name)
static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan< unsigned char > qbuffer, ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
std::array< unsigned char,(QueryBufferSize+15)/16 *16 > QueryBuffer
static constexpr qsizetype QueryBufferSize
static int prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
static constexpr unsigned char Edns0Record[]
bool setIpv6NameServer(State *state, EnableIfIPv6< sizeof(std::declval< State >()._u._ext.nsaddrs) !=0 > addr, quint16 port)
#define T_OPT
std::enable_if_t< Condition, const QHostAddress * > EnableIfIPv6
void setNsMap(T &ext, std::enable_if_t< sizeof(T::nsmap) !=0, uint16_t > v)
EGLOutputPortEXT port
Q_DECL_COLD_FUNCTION Q_CORE_EXPORT QString qt_error_string(int errorCode=-1)
GLsizei const GLfloat * v
[13]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLuint GLuint GLfloat weight
GLenum GLuint buffer
GLenum type
GLuint GLsizei const GLchar * label
[43]
GLenum GLuint GLintptr offset
GLuint name
GLfloat n
GLenum query
GLenum const void * addr
GLsizeiptr const void GLenum usage
Definition qopenglext.h:543
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define QT_REQUIRE_CONFIG(feature)
@ Q_RELOCATABLE_TYPE
Definition qtypeinfo.h:158
#define Q_DECLARE_TYPEINFO(TYPE, FLAGS)
Definition qtypeinfo.h:180
unsigned int quint32
Definition qtypes.h:50
unsigned char uchar
Definition qtypes.h:32
short qint16
Definition qtypes.h:47
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qptrdiff
Definition qtypes.h:164
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned char quint8
Definition qtypes.h:46
Q_CHECK_PTR(a=new int[80])
QFileSelector selector
[1]
MyRecord record(int row) const
[0]
Text files * txt
QNetworkReply * reply