Kstars

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

KDE's Doxygen guidelines are available online.