Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
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 <qurl.h>
10#include <qvarlengtharray.h>
11#include <private/qnativesocketengine_p.h> // for setSockAddr
12#include <private/qtnetwork-config_p.h>
13
15
16#include <sys/types.h>
17#include <netinet/in.h>
18#include <arpa/nameser.h>
19#if __has_include(<arpa/nameser_compat.h>)
20# include <arpa/nameser_compat.h>
21#endif
22#include <errno.h>
23#include <resolv.h>
24
25#include <array>
26
27#ifndef T_OPT
28// the older arpa/nameser_compat.h wasn't updated between 1999 and 2016 in glibc
29# define T_OPT ns_t_opt
30#endif
31
33
34using namespace Qt::StringLiterals;
35
36// minimum IPv6 MTU (1280) minus the IPv6 (40) and UDP headers (8)
37static constexpr qsizetype ReplyBufferSize = 1280 - 40 - 8;
38
39// https://www.rfc-editor.org/rfc/rfc6891
40static constexpr unsigned char Edns0Record[] = {
41 0x00, // root label
42 T_OPT >> 8, T_OPT & 0xff, // type OPT
43 ReplyBufferSize >> 8, ReplyBufferSize & 0xff, // payload size
44 NOERROR, // extended rcode
45 0, // version
46 0x00, 0x00, // flags
47 0x00, 0x00, // option length
48};
49
50// maximum length of a EDNS0 query with a 255-character domain (rounded up to 16)
51static constexpr qsizetype QueryBufferSize =
52 HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 + sizeof(Edns0Record);
53using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>;
54
55namespace {
56struct QDnsCachedName
57{
59 int code = 0;
60 QDnsCachedName(const QString &name, int code) : name(name), code(code) {}
61};
62}
64using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
65
66#if QT_CONFIG(res_setservers)
67// https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html
68// https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html
69static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
70{
71 if (!nameserver.isNull()) {
72 union res_sockaddr_union u;
73 setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
74 res_setservers(state, &u, 1);
75 }
76 return true;
77}
78#else
79template <typename T> void setNsMap(T &ext, std::enable_if_t<sizeof(T::nsmap) != 0, uint16_t> v)
80{
81 // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
82 // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
83 // Unneeded since glibc 2.22 (2015), but doesn't hurt to set it
84 // See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7
85 ext.nsmap[0] = v;
86}
87template <typename T> void setNsMap(T &, ...)
88{
89 // fallback
90}
91
92template <bool Condition>
93using EnableIfIPv6 = std::enable_if_t<Condition, const QHostAddress *>;
94
95template <typename State>
97 EnableIfIPv6<sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
99{
100 // glibc-like API to set IPv6 name servers
101 struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
102
103 // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
104 if (!ns) {
105 // Memory allocated here will be free()'d in res_close() as we
106 // have done res_init() above.
107 ns = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
109 state->_u._ext.nsaddrs[0] = ns;
110 }
111
112 setNsMap(state->_u._ext, MAXNS + 1);
113 state->_u._ext.nscount6 = 1;
114 setSockaddr(ns, *addr, port);
115 return true;
116}
117
118template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
119{
120 // fallback
121 return false;
122}
123
124static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
125{
126 if (nameserver.isNull())
127 return true;
128
129 state->nscount = 1;
130 state->nsaddr_list[0].sin_family = AF_UNSPEC;
131 if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
132 return setIpv6NameServer(state, &nameserver, port);
133 setSockaddr(&state->nsaddr_list[0], nameserver, port);
134 return true;
135}
136#endif // !QT_CONFIG(res_setservers)
137
138static int
139prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
140{
141 // Create header and our query
142 int queryLength = res_nmkquery(state, QUERY, label, C_IN, type, nullptr, 0, nullptr,
143 buffer.data(), buffer.size());
144 Q_ASSERT(queryLength < int(buffer.size()));
145 if (Q_UNLIKELY(queryLength < 0))
146 return queryLength;
147
148 // Append EDNS0 record and set the number of additional RRs to 1
149 Q_ASSERT(queryLength + sizeof(Edns0Record) < buffer.size());
150 std::copy_n(std::begin(Edns0Record), sizeof(Edns0Record), buffer.begin() + queryLength);
151 reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
152
153 return queryLength + sizeof(Edns0Record);
154}
155
156void QDnsLookupRunnable::query(QDnsLookupReply *reply)
157{
158 // Initialize state.
159 std::remove_pointer_t<res_state> state = {};
160 if (res_ninit(&state) < 0) {
161 int error = errno;
162 qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
163 return reply->makeResolverSystemError(error);
164 }
165 auto guard = qScopeGuard([&] { res_nclose(&state); });
166
167 //Check if a nameserver was set. If so, use it
168 if (!applyNameServer(&state, nameserver, port))
170 QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
171#ifdef QDNSLOOKUP_DEBUG
172 state.options |= RES_DEBUG;
173#endif
174
175 // Prepare the DNS query.
176 QueryBuffer qbuffer;
177 int queryLength = prepareQueryBuffer(&state, qbuffer, requestName, ns_rcode(requestType));
178 if (Q_UNLIKELY(queryLength < 0))
179 return reply->makeResolverSystemError();
180
181 // Perform DNS query.
183 auto attemptToSend = [&]() {
184 std::memset(buffer.data(), 0, HFIXEDSZ); // the header is enough
185 int responseLength = res_nsend(&state, qbuffer.data(), queryLength, buffer.data(), buffer.size());
186 if (responseLength < 0) {
187 // network error of some sort
188 if (errno == ETIMEDOUT)
189 reply->makeTimeoutError();
190 else
191 reply->makeResolverSystemError();
192 }
193 return responseLength;
194 };
195
196 // strictly use UDP, we'll deal with truncated replies ourselves
197 state.options |= RES_IGNTC;
198 int responseLength = attemptToSend();
199 if (responseLength < 0)
200 return;
201
202 // check if we need to use the virtual circuit (TCP)
203 auto header = reinterpret_cast<HEADER *>(buffer.data());
204 if (header->rcode == NOERROR && header->tc) {
205 // yes, increase our buffer size
206 buffer.resize(std::numeric_limits<quint16>::max());
207 header = reinterpret_cast<HEADER *>(buffer.data());
208
209 // remove the EDNS record in the query
210 reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
211 queryLength -= sizeof(Edns0Record);
212
213 // send using the virtual circuit
214 state.options |= RES_USEVC;
215 responseLength = attemptToSend();
216 if (Q_UNLIKELY(responseLength > buffer.size())) {
217 // Ok, we give up.
219 QDnsLookup::tr("Reply was too large"));
220 }
221 }
222 if (responseLength < 0)
223 return;
224
225 // Check the reply is valid.
226 if (responseLength < int(sizeof(HEADER)))
227 return reply->makeInvalidReplyError();
228
229 // Parse the reply.
230 if (header->rcode)
231 return reply->makeDnsRcodeError(header->rcode);
232
233 qptrdiff offset = sizeof(HEADER);
234 unsigned char *response = buffer.data();
235 int status;
236
237 auto expandHost = [&, cache = Cache{}](qptrdiff offset) mutable {
238 if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
239 // compressed name, see if we already have it cached
240 if (offset + 1 < responseLength) {
241 int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
242 auto it = std::find_if(cache.constBegin(), cache.constEnd(),
243 [id](const QDnsCachedName &n) { return n.code == id; });
244 if (it != cache.constEnd()) {
245 status = 2;
246 return it->name;
247 }
248 }
249 }
250
251 // uncached, expand it
252 char host[MAXCDNAME + 1];
253 status = dn_expand(response, response + responseLength, response + offset,
254 host, sizeof(host));
255 if (status >= 0)
256 return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
257
258 // failed
259 reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
260 return QString();
261 };
262
263 if (ntohs(header->qdcount) == 1) {
264 // Skip the query host, type (2 bytes) and class (2 bytes).
265 expandHost(offset);
266 if (status < 0)
267 return;
268 if (offset + status + 4 >= responseLength)
269 header->qdcount = 0xffff; // invalid reply below
270 else
271 offset += status + 4;
272 }
273 if (ntohs(header->qdcount) > 1)
274 return reply->makeInvalidReplyError();
275
276 // Extract results.
277 const int answerCount = ntohs(header->ancount);
278 int answerIndex = 0;
279 while ((offset < responseLength) && (answerIndex < answerCount)) {
280 const QString name = expandHost(offset);
281 if (status < 0)
282 return;
283
284 offset += status;
285 if (offset + RRFIXEDSZ > responseLength) {
286 // probably just a truncated reply, return what we have
287 return;
288 }
289 const quint16 type = qFromBigEndian<quint16>(response + offset);
290 const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
291 const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
292 const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
293 offset += RRFIXEDSZ;
294 if (offset + size > responseLength)
295 return; // truncated
296 if (rrclass != C_IN)
297 continue;
298
299 if (type == QDnsLookup::A) {
300 if (size != 4)
301 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv4 address record"));
302 const quint32 addr = qFromBigEndian<quint32>(response + offset);
304 record.d->name = name;
305 record.d->timeToLive = ttl;
306 record.d->value = QHostAddress(addr);
307 reply->hostAddressRecords.append(record);
308 } else if (type == QDnsLookup::AAAA) {
309 if (size != 16)
310 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv6 address record"));
312 record.d->name = name;
313 record.d->timeToLive = ttl;
314 record.d->value = QHostAddress(response + offset);
315 reply->hostAddressRecords.append(record);
316 } else if (type == QDnsLookup::CNAME) {
318 record.d->name = name;
319 record.d->timeToLive = ttl;
320 record.d->value = expandHost(offset);
321 if (status < 0)
322 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
323 reply->canonicalNameRecords.append(record);
324 } else if (type == QDnsLookup::NS) {
326 record.d->name = name;
327 record.d->timeToLive = ttl;
328 record.d->value = expandHost(offset);
329 if (status < 0)
330 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
331 reply->nameServerRecords.append(record);
332 } else if (type == QDnsLookup::PTR) {
334 record.d->name = name;
335 record.d->timeToLive = ttl;
336 record.d->value = expandHost(offset);
337 if (status < 0)
338 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
339 reply->pointerRecords.append(record);
340 } else if (type == QDnsLookup::MX) {
341 const quint16 preference = qFromBigEndian<quint16>(response + offset);
343 record.d->exchange = expandHost(offset + 2);
344 record.d->name = name;
345 record.d->preference = preference;
346 record.d->timeToLive = ttl;
347 if (status < 0)
348 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
349 reply->mailExchangeRecords.append(record);
350 } else if (type == QDnsLookup::SRV) {
351 const quint16 priority = qFromBigEndian<quint16>(response + offset);
352 const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
353 const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
355 record.d->name = name;
356 record.d->target = expandHost(offset + 6);
357 record.d->port = port;
358 record.d->priority = priority;
359 record.d->timeToLive = ttl;
360 record.d->weight = weight;
361 if (status < 0)
362 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
363 reply->serviceRecords.append(record);
364 } else if (type == QDnsLookup::TXT) {
366 record.d->name = name;
367 record.d->timeToLive = ttl;
369 while (txt < offset + size) {
370 const unsigned char length = response[txt];
371 txt++;
372 if (txt + length > offset + size)
373 return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid text record"));
374 record.d->values << QByteArrayView(response + txt, length).toByteArray();
375 txt += length;
376 }
377 reply->textRecords.append(record);
378 }
379 offset += size;
380 answerIndex++;
381 }
382}
383
[qjs-as-container]
static constexpr auto IPv6Protocol
QByteArray toByteArray() const
Definition qbytearray.h:709
The QDnsDomainNameRecord class stores information about a domain name record.
Definition qdnslookup.h:27
The QDnsHostAddressRecord class stores information about a host address record.
Definition qdnslookup.h:49
The QDnsMailExchangeRecord class stores information about a DNS MX record.
Definition qdnslookup.h:71
The QDnsServiceRecord class stores information about a DNS SRV record.
Definition qdnslookup.h:94
The QDnsTextRecord class stores information about a DNS TXT record.
Definition qdnslookup.h:119
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.
Definition qlist.h:74
void setError(NetworkError errorCode, const QString &errorString)
Sets the error condition to be errorCode.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
void resize(qsizetype size)
Sets the size of the string to size characters.
Definition qstring.cpp:2654
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 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)
static constexpr qsizetype ReplyBufferSize
#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
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 const void * addr
#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:145
#define Q_DECLARE_TYPEINFO(TYPE, FLAGS)
Definition qtypeinfo.h:163
unsigned int quint32
Definition qtypes.h:45
unsigned char uchar
Definition qtypes.h:27
short qint16
Definition qtypes.h:42
unsigned short quint16
Definition qtypes.h:43
ptrdiff_t qptrdiff
Definition qtypes.h:69
ptrdiff_t qsizetype
Definition qtypes.h:70
Q_CHECK_PTR(a=new int[80])
MyRecord record(int row) const
[0]
Text files * txt
QNetworkReply * reply