1 /*
2     SPDX-FileCopyrightText: 2016 Alex Spataru
3     SPDX-License-Identifier: MIT
4 */
5 
6 #include "qMDNS.h"
7 
8 #include <QHostInfo>
9 #include <QUdpSocket>
10 #include <QHostAddress>
11 #include <QNetworkInterface>
12 
13 #ifdef Q_OS_LINUX
14     #include <sys/socket.h>
15 #endif
16 
17 #include "kstars_debug.h"
18 /*
19  * DNS port and mutlicast addresses
20  */
21 const quint16 MDNS_PORT = 5353;
22 const QHostAddress IPV6_ADDRESS = QHostAddress ("FF02::FB");
23 const QHostAddress IPV4_ADDRESS = QHostAddress ("224.0.0.251");
24 
25 /*
26  * mDNS/DNS operation flags
27  */
28 const quint16 kQR_Query       = 0x0000;
29 const quint16 kQR_Response    = 0x8000;
30 const quint16 kRecordA        = 0x0001;
31 const quint16 kRecordAAAA     = 0x001C;
32 const quint16 kNsecType       = 0x002F;
33 const quint16 kFQDN_Separator = 0x0000;
34 const quint16 kFQDN_Length    = 0xC00C;
35 const quint16 kIN_BitFlush    = 0x8001;
36 const quint16 kIN_Normal      = 0x0001;
37 
38 /*
39  * DNS query properties
40  */
41 const quint16 kQuery_QDCOUNT = 0x02;
42 const quint16 kQuery_ANCOUNT = 0x00;
43 const quint16 kQuery_NSCOUNT = 0x00;
44 const quint16 kQuery_ARCOUNT = 0x00;
45 
46 /*
47  * DNS response properties
48  */
49 const quint16 kResponse_QDCOUNT = 0x00;
50 const quint16 kResponse_ANCOUNT = 0x01;
51 const quint16 kResponse_NSCOUNT = 0x00;
52 const quint16 kResponse_ARCOUNT = 0x02;
53 
54 /* Packet constants */
55 const int MIN_LENGTH = 13;
56 const int IPI_LENGTH = 10;
57 const int IP4_LENGTH = IPI_LENGTH + 4;
58 const int IP6_LENGTH = IPI_LENGTH + 16;
59 
60 /**
61  * Encodes the 16-bit \a number as two 8-bit numbers in a byte array
62  */
ENCODE_16_BIT(quint16 number)63 QByteArray ENCODE_16_BIT (quint16 number) {
64     QByteArray data;
65     data.append ((number & 0xff00) >> 8);
66     data.append ((number & 0xff));
67     return data;
68 }
69 
70 /**
71  * Encodes the 32-bit \a number as four 8-bit numbers
72  */
ENCODE_32_BIT(quint32 number)73 QByteArray ENCODE_32_BIT (quint32 number) {
74     QByteArray data;
75     data.append ((number & 0xff000000UL) >> 24);
76     data.append ((number & 0x00ff0000UL) >> 16);
77     data.append ((number & 0x0000ff00UL) >>  8);
78     data.append ((number & 0x000000ffUL));
79     return data;
80 }
81 
82 /**
83  * Obtains the 16-bit number stored in the \a upper and \a lower 8-bit numbers
84  */
DECODE_16_BIT(quint8 upper,quint8 lower)85 quint16 DECODE_16_BIT (quint8 upper, quint8 lower) {
86     return (quint16) ((upper << 8) | lower);
87 }
88 
89 /**
90  * Binds the given \a socket to the given \a address and \a port.
91  * Under GNU/Linux, this function implements a workaround of QTBUG-33419.
92  */
BIND(QUdpSocket * socket,const QHostAddress & address,const int port)93 bool BIND (QUdpSocket* socket, const QHostAddress& address, const int port) {
94     if (!socket)
95         return false;
96 
97 #ifdef Q_OS_LINUX
98     int reuse = 1;
99     int domain = PF_UNSPEC;
100 
101     if (address.protocol() == QAbstractSocket::IPv4Protocol)
102         domain = PF_INET;
103     else if (address.protocol() == QAbstractSocket::IPv6Protocol)
104         domain = PF_INET6;
105 
106     socket->setSocketDescriptor (::socket (domain, SOCK_DGRAM, 0),
107                                  QUdpSocket::UnconnectedState);
108 
109     setsockopt (socket->socketDescriptor(), SOL_SOCKET, SO_REUSEADDR,
110                 &reuse, sizeof (reuse));
111 #endif
112 
113     return socket->bind (address, port,
114                          QUdpSocket::ShareAddress |
115                          QUdpSocket::ReuseAddressHint);
116 }
117 
qMDNS()118 qMDNS::qMDNS() {
119     /* Set default TTL to 4500 seconds */
120     m_ttl = 4500;
121 
122     /* Initialize sockets */
123     m_IPv4Socket = new QUdpSocket (this);
124     m_IPv6Socket = new QUdpSocket (this);
125 
126     /* Read and interpret data received from mDNS group */
127     connect (m_IPv4Socket, &QUdpSocket::readyRead, this, &qMDNS::onReadyRead);
128     connect (m_IPv6Socket, &QUdpSocket::readyRead, this, &qMDNS::onReadyRead);
129 
130     /* Bind the sockets to the mDNS multicast group */
131     if (BIND (m_IPv4Socket, QHostAddress::AnyIPv4, MDNS_PORT))
132         m_IPv4Socket->joinMulticastGroup (IPV4_ADDRESS);
133     if (BIND (m_IPv6Socket, QHostAddress::AnyIPv6, MDNS_PORT))
134         m_IPv6Socket->joinMulticastGroup (IPV6_ADDRESS);
135 }
136 
~qMDNS()137 qMDNS::~qMDNS() {
138     delete m_IPv4Socket;
139     delete m_IPv6Socket;
140 }
141 
142 /**
143  * Returns the only running instance of this class
144  */
getInstance()145 qMDNS* qMDNS::getInstance() {
146     static qMDNS instance;
147     return &instance;
148 }
149 
150 /**
151  * Returns the mDNS name assigned to the client computer
152  */
hostName() const153 QString qMDNS::hostName() const {
154     return m_hostName;
155 }
156 
157 /**
158  * Ensures that the given \a string is a valid mDNS/DNS address.
159  */
getAddress(const QString & string)160 QString qMDNS::getAddress (const QString& string) {
161     QString address = string;
162 
163     if (!string.endsWith (".local") && !string.contains ("."))
164         address = string + ".local";
165 
166     if (string.endsWith ("."))
167         return "";
168 
169     return address;
170 }
171 
172 /**
173  * Changes the TTL send to other computers in the mDNS network
174  */
setTTL(const quint32 ttl)175 void qMDNS::setTTL (const quint32 ttl) {
176     m_ttl = ttl;
177 }
178 
179 /**
180  * Performs a mDNS lookup to find the given host \a name.
181  * If \a preferIPv6 is set to \c true, then this function will generate a
182  * packet that requests an AAAA-type Resource Record instead of an A-type
183  * Resource Record.
184  */
lookup(const QString & name)185 void qMDNS::lookup (const QString& name) {
186     /* The host name is empty, abort lookup */
187     if (name.isEmpty()) {
188         qCWarning(KSTARS) << Q_FUNC_INFO << "Empty host name specified";
189         return;
190     }
191 
192     qCInfo(KSTARS) << "Starting lookup for service" << name;
193 
194     m_serviceName = name;
195 
196     /* Ensure that we host name is a valid DNS address */
197     QString address = getAddress (name);
198     if (address.isEmpty())
199         return;
200 
201     /* Check if we are dealing with a normal DNS address */
202     if (!address.endsWith (".local", Qt::CaseInsensitive)) {
203         QHostInfo::lookupHost (address, this, SIGNAL (hostFound(QHostInfo)));
204         return;
205     }
206 
207     /* Perform a mDNS lookup */
208     else {
209         QByteArray data;
210 
211         /* Get the host name and domain */
212         QString host = address.split (".").first();
213         QString domain = address.split (".").last();
214 
215         /* Check that domain length is valid */
216         if (host.length() > 255) {
217             qWarning() << Q_FUNC_INFO << host << "is too long!";
218             return;
219         }
220 
221         /* Create header & flags */
222         data.append (ENCODE_16_BIT (0));
223         data.append (ENCODE_16_BIT (kQR_Query));
224         data.append (ENCODE_16_BIT (kQuery_QDCOUNT));
225         data.append (ENCODE_16_BIT (kQuery_ANCOUNT));
226         data.append (ENCODE_16_BIT (kQuery_NSCOUNT));
227         data.append (ENCODE_16_BIT (kQuery_ARCOUNT));
228 
229         /* Add name data */
230         data.append (host.length());
231         data.append (host.toUtf8());
232 
233         /* Add domain data */
234         data.append (domain.length());
235         data.append (domain.toUtf8());
236 
237         /* Add FQDN/TLD separator */
238         data.append ((char) kFQDN_Separator);
239 
240         /* Add IPv4 record type */
241         data.append (ENCODE_16_BIT (kRecordA));
242         data.append (ENCODE_16_BIT (kIN_Normal));
243 
244         /* Add FQDN length */
245         data.append (ENCODE_16_BIT (kFQDN_Length));
246 
247         /* Add IPv6 record type */
248         data.append (ENCODE_16_BIT (kRecordAAAA));
249         data.append (ENCODE_16_BIT (kIN_Normal));
250 
251         /* Send the datagram */
252         sendPacket (data);
253     }
254 }
255 
256 /**
257  * Changes the host name of the client computer
258  */
setHostName(const QString & name)259 void qMDNS::setHostName (const QString& name) {
260     if (name.contains (".") && !name.endsWith (".local")) {
261         qWarning() << "Invalid domain name";
262         return;
263     }
264 
265     m_hostName = getAddress (name);
266 }
267 
268 /**
269  * Called when we receive data from a mDNS client on the network.
270  */
onReadyRead()271 void qMDNS::onReadyRead() {
272     QByteArray data;
273     QUdpSocket* socket = qobject_cast<QUdpSocket*> (sender());
274 
275     /* Read data from the socket */
276     if (socket) {
277         while (socket->hasPendingDatagrams()) {
278             data.resize (socket->pendingDatagramSize());
279             socket->readDatagram (data.data(), data.size());
280         }
281     }
282 
283     /* Packet is a valid mDNS datagram */
284     if (data.length() > MIN_LENGTH) {
285         quint16 flag = DECODE_16_BIT (data.at (2), data.at (3));
286 
287         if (flag == kQR_Query)
288             readQuery (data);
289 
290         else if (flag >= kQR_Response)
291             readResponse (data);
292     }
293 }
294 
295 /**
296  * Reads the given query \a data and instructs the class to send a response
297  * packet if the query is looking for the host name assigned to this computer.
298  */
readQuery(const QByteArray & data)299 void qMDNS::readQuery (const QByteArray& data) {
300     /* Query packet is invalid */
301     if (data.length() < MIN_LENGTH)
302         return;
303 
304     /* Get the lengths of the host name and domain */
305     int n = 12;
306     int hostLength = data.at (n);
307     int domainLength = data.at (n + hostLength + 1);
308 
309     /* Read the host name until we stumble with the domain length character */
310     QString name;
311     int h = n + 1;
312     while (data.at (h) != (char) domainLength) {
313         name.append (data.at (h));
314         ++h;
315     }
316 
317     /* Read domain length until we stumble with the FQDN/TLD separator */
318     QString domain;
319     int d = n + hostLength + 2;
320     while (data.at (d) != kFQDN_Separator) {
321         domain.append (data.at (d));
322         ++d;
323     }
324 
325     /* Construct the full host name (name + domain) */
326     QString host = getAddress (name + "." + domain);
327 
328     /* The query packet wants to know more about us */
329     if (host.toLower() == hostName().toLower())
330         sendResponse (DECODE_16_BIT (data.at (0), data.at (1)));
331 }
332 
333 /**
334  * Sends the given \a data to both the IPv4 and IPv6 mDNS multicast groups
335  */
sendPacket(const QByteArray & data)336 void qMDNS::sendPacket (const QByteArray& data) {
337     if (!data.isEmpty()) {
338         m_IPv4Socket->writeDatagram (data, IPV4_ADDRESS, MDNS_PORT);
339         m_IPv6Socket->writeDatagram (data, IPV6_ADDRESS, MDNS_PORT);
340     }
341 }
342 
343 /**
344  * Reads the given \a data of a response packet and obtains:
345  * - The remote host name
346  * - The remote IPv4
347  * - The remote IPv6
348  */
readResponse(const QByteArray & data)349 void qMDNS::readResponse (const QByteArray& data) {
350     if (data.length() < MIN_LENGTH)
351         return;
352 
353     qCDebug(KSTARS) << data;
354 
355     // data must contain service name
356     if (data.contains(m_serviceName.toLatin1()) == false)
357         return;
358 
359     QString host = getHostNameFromResponse (data);
360     QList<QHostAddress> addresses = getAddressesFromResponse (data, host);
361 
362     if (!host.isEmpty() && !addresses.isEmpty())
363     {
364         QHostInfo info;
365         info.setHostName (host);
366         info.setAddresses (addresses);
367         info.setError (QHostInfo::NoError);
368 
369         qCInfo(KSTARS) << "Found service on" << host;
370 
371         emit hostFound (info);
372     }
373 }
374 
375 /**
376  * Sends a response packet with:
377  * - Our mDNS host name
378  * - Our IPv4 address
379  * - Our IPv6 address
380  */
sendResponse(const quint16 query_id)381 void qMDNS::sendResponse (const quint16 query_id) {
382     if (!hostName().isEmpty() && hostName().endsWith (".local")) {
383         QByteArray data;
384 
385         /* Get the host name and domain */
386         QString host = hostName().split (".").first();
387         QString domain = hostName().split (".").last();
388 
389         /* Get local IPs */
390         quint32 ipv4 = 0;
391         QList<QIPv6Address> ipv6;
392         foreach (QHostAddress address, QNetworkInterface::allAddresses()) {
393             if (!address.isLoopback()) {
394                 if (address.protocol() == QAbstractSocket::IPv4Protocol)
395                     ipv4 = (ipv4 == 0 ? address.toIPv4Address() : ipv4);
396 
397                 if (address.protocol() == QAbstractSocket::IPv6Protocol)
398                     ipv6.append (address.toIPv6Address());
399             }
400         }
401 
402         /* Check that domain length is valid */
403         if (host.length() > 255) {
404             qCWarning(KSTARS) << Q_FUNC_INFO << host << "is too long!";
405             return;
406         }
407 
408         /* Create header and flags */
409         data.append (ENCODE_16_BIT (query_id));
410         data.append (ENCODE_16_BIT (kQR_Response));
411         data.append (ENCODE_16_BIT (kResponse_QDCOUNT));
412         data.append (ENCODE_16_BIT (kResponse_ANCOUNT));
413         data.append (ENCODE_16_BIT (kResponse_NSCOUNT));
414         data.append (ENCODE_16_BIT (kResponse_ARCOUNT));
415 
416         /* Add name data */
417         data.append (host.length());
418         data.append (host.toUtf8());
419 
420         /* Add domain data and FQDN/TLD separator */
421         data.append (domain.length());
422         data.append (domain.toUtf8());
423         data.append ((char) kFQDN_Separator);
424 
425         /* Add IPv4 address header */
426         data.append (ENCODE_16_BIT (kRecordA));
427         data.append (ENCODE_16_BIT (kIN_BitFlush));
428         data.append (ENCODE_32_BIT (m_ttl));
429         data.append (ENCODE_16_BIT (sizeof (ipv4)));
430 
431         /* Add IPv4 bytes */
432         data.append (ENCODE_32_BIT (ipv4));
433 
434         /* Add FQDN offset */
435         data.append (ENCODE_16_BIT (kFQDN_Length));
436 
437         /* Add IPv6 addresses */
438         foreach (QIPv6Address ip, ipv6) {
439             data.append (ENCODE_16_BIT (kRecordAAAA));
440             data.append (ENCODE_16_BIT (kIN_BitFlush));
441             data.append (ENCODE_32_BIT (m_ttl));
442             data.append (ENCODE_16_BIT (sizeof (ip.c)));
443 
444             /* Add IPv6 bytes */
445             for (unsigned long i = 0; i < sizeof (ip.c); ++i)
446                 data.append (ip.c [i]);
447 
448             /* Add FQDN offset */
449             data.append (ENCODE_16_BIT (kFQDN_Length));
450         }
451 
452         /* TODO: Generate NSEC code block */
453         int nsec_length = 0;
454 
455         /* Add NSEC data */
456         data.append (ENCODE_16_BIT (kNsecType));
457         data.append (ENCODE_16_BIT (kIN_BitFlush));
458         data.append (ENCODE_32_BIT (m_ttl));
459         data.append (ENCODE_16_BIT (nsec_length));
460 
461         /* Send the response */
462         sendPacket (data);
463     }
464 }
465 
466 /**
467  * Extracts the host name from the \a data received from the mDNS network.
468  * The host name begins at byte #12 (when the header and flags end) and ends
469  * with a mandatory NUL character after the domain.
470  *
471  * The host name is constructed in the following way (without spaces):
472  * \c NAME_LENGTH + \c NAME + \c DOMAIN_LENGTH + \c DOMAIN + \c NUL
473  *
474  * For example, appletv.local would be formatted as:
475  * \c 0x07 + \c appletv + \c 0x05 + \c local + \c 0x00
476  *
477  * Or, if you prefer hex data:
478  * \c { 07 61 70 70 6c 65 74 76 05 6c 6f 63 61 6c 00 }
479  * \c {  7 a  p  p  l  e  t  v   5 l  o  c  a  l   0 }
480  *
481  * In order to obtain the full host name (and its mDNS domain), we construct
482  * the string backwards. When the code notices that the current character is
483  * the same as the domain length, we know that the domain name has been
484  * extracted, and thus we can replace the domain length with a dot (.) and
485  * begin extracting the host name.
486  */
getHostNameFromResponse(const QByteArray & data)487 QString qMDNS::getHostNameFromResponse (const QByteArray& data) {
488     QList<char> list;
489     QString address = "";
490 
491     /* Begin reading host name at byte 13 (byte 12 is the host name length) */
492     int n = 13;
493 
494     /* Read the host name until we stumble with the FQDN/TLD separator */
495     while (data.at (n) != kFQDN_Separator) {
496         list.append (data.at (n));
497         ++n;
498     }
499 
500     /* Construct the string backwards (to replace domain length with a dot) */
501     for (int i = 0; i < list.count(); ++i) {
502         char character = list.at (list.count() - i - 1);
503 
504         if (character == (char) address.length())
505             address.prepend (".");
506         else
507             address.prepend (character);
508     }
509 
510     return address;
511 }
512 
513 /**
514  * Extracts the IPv4 from the \a data received from the mDNS network.
515  * The IPv4 data begins when the host name data ends.
516  *
517  * For the packet to contain IPv4 information, the DNS Record Type code must
518  * be "A" (IPv4) and the DNS Class code should correspond to "IN" (Internet).
519  *
520  * Here is the layout of the IPv4 section of the packet:
521  *
522  * - DNS Record Type
523  * - DNS Class Code
524  * - TTL
525  * - IP length
526  * - IP address bytes
527  *
528  * This is an example IPv4 section:
529  * \c {00 01 80 01 00 00 78 00 00 04 99 6d 07 5a}
530  *
531  * Data in example section:
532  * - \c {00 01} Type Codes
533  * - \c {80 01} Class Codes
534  * - \c {00 00 78 00} IP TTL
535  * - \c {00 04} Number of address bytes (length in layman's terms)
536  * - \c {99 6d 07 5a} IPv4 Address bytes (153, 109, 7, 90)
537  */
getIPv4FromResponse(const QByteArray & data,const QString & host)538 QString qMDNS::getIPv4FromResponse (const QByteArray& data,
539                                     const QString& host) {
540     QString ip = "";
541 
542     /* n stands for the byte index in which the host name data ends */
543     int n = MIN_LENGTH + host.length();
544 
545     /* Packet is too small */
546     if (data.length() < n + IP4_LENGTH)
547         return ip;
548 
549     /* Get the IP type and class codes */
550     quint16 typeCode  = DECODE_16_BIT (data.at (n + 1), data.at (n + 2));
551     quint16 classCode = DECODE_16_BIT (data.at (n + 3), data.at (n + 4));
552 
553     /* Check if type and class codes are good */
554     if (typeCode != kRecordA || classCode != kIN_BitFlush)
555         return ip;
556 
557     /* Skip TTL indicator and obtain the number of address bytes */
558     quint8 length = data.at (n + IPI_LENGTH);
559 
560     /* Append each IPv4 address byte (and decimal dots) to the IP string */
561     for (int i = 1; i < length + 1; ++i) {
562         ip += QString::number ((quint8) data.at (n + IPI_LENGTH + i));
563         ip += (i < length) ? "." : "";
564     }
565 
566     return ip;
567 }
568 
569 /**
570  * Extracts the IPv6 from the \a data received from the mDNS network.
571  * The IPv6 data begins when the host name data ends.
572  *
573  * For the packet to contain IPv6 information, the DNS Record Type code must
574  * be "AAAA" (IPv6) and the DNS Class code should correspond to "IN" (Internet).
575  *
576  * Here is the layout of the IPv4 section of the packet:
577  *
578  * - DNS Record Type
579  * - DNS Class Code
580  * - TTL
581  * - IP length
582  * - IP address bytes
583  *
584  * This is an example IPv6 section:
585  * \c { 00 1c 80 01 00 00 78 00 00 10 fe 80 00 00 00 00 00 00 02 23 32 ff fe b1 21 52 }
586  *
587  * Data in example section:
588  * - \c {00 1c} Type Codes
589  * - \c {80 01} Class Codes
590  * - \c {00 00 78 00} IP TTL
591  * - \c {00 10} Number of address bytes (length in layman's terms)
592  * - \c {fe 80 00 00 ... 52} IPv6 Address bytes (there are 16 of them)
593  */
getIPv6FromResponse(const QByteArray & data,const QString & host)594 QStringList qMDNS::getIPv6FromResponse (const QByteArray& data,
595                                         const QString& host) {
596     QStringList list;
597 
598     /* Skip the FQDN and IPv4 section */
599     int n = MIN_LENGTH + IP4_LENGTH + host.length();
600 
601     /* Get the IPv6 list */
602     bool isIPv6 = true;
603     while (isIPv6) {
604         /* Skip FQDN bytes */
605         n += 2;
606 
607         /* Packet is invalid */
608         if (data.length() < n + IP6_LENGTH)
609             break;
610 
611         /* Get the IP type and class codes */
612         quint16 typeCode  = DECODE_16_BIT (data.at (n + 1), data.at (n + 2));
613         quint16 classCode = DECODE_16_BIT (data.at (n + 3), data.at (n + 4));
614         isIPv6 = (typeCode == kRecordAAAA && classCode == kIN_BitFlush);
615 
616         /* IP type and class codes are OK, extract IP */
617         if (isIPv6) {
618             /* Skip TTL indicator and obtain the number of address bytes */
619             quint8 length = data.at (n + IPI_LENGTH);
620 
621             /* Append each IPv6 address byte (encoded as hex) to the IP string */
622             QString ip = "";
623             for (int i = 1; i < length + 1; ++i) {
624                 /* Get the hexadecimal representation of the byte */
625                 QString byte;
626                 byte.setNum ((quint8) data.at (n + i + IPI_LENGTH), 16);
627 
628                 /* Add the obtained string */
629                 ip += byte;
630 
631                 /* Append colons after even indexes (except in the last byte) */
632                 if ((i & 1) == 0 && (i < length))
633                     ip += ":";
634             }
635 
636             /* Increase the counter to 'jump' to the next section */
637             n += 26;
638 
639             /* Append the obtained IP to the list */
640             if (!list.contains (ip))
641                 list.append (ip);
642         }
643     }
644 
645     return list;
646 }
647 
648 /**
649  * Obtains the IPv4 and IPv6 addresses from the received data.
650  * \note This function will only generate a list with the valid IP addresses.
651  */
getAddressesFromResponse(const QByteArray & data,const QString & host)652 QList<QHostAddress> qMDNS::getAddressesFromResponse (const QByteArray& data,
653         const QString& host) {
654     QList<QHostAddress> list;
655 
656     /* Add IPv4 address */
657     QHostAddress IPv4Address = QHostAddress (getIPv4FromResponse (data, host));
658     if (!IPv4Address.isNull())
659         list.append (IPv4Address);
660 
661     /* Add IPv6 addresses */
662     foreach (QString ip, getIPv6FromResponse (data, host)) {
663         QHostAddress address = QHostAddress (ip);
664         if (!address.isNull())
665             list.append (address);
666     }
667 
668     return list;
669 }
670