1 /*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2017 Nathan Osman
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #include <QtGlobal>
26
27 #ifdef Q_OS_UNIX
28 # include <cerrno>
29 # include <cstring>
30 # include <sys/socket.h>
31 #endif
32
33 #include <QHostAddress>
34 #include <QNetworkInterface>
35
36 #include <qmdnsengine/dns.h>
37 #include <qmdnsengine/mdns.h>
38 #include <qmdnsengine/message.h>
39 #include <qmdnsengine/server.h>
40
41 #include "server_p.h"
42
43 using namespace QMdnsEngine;
44
ServerPrivate(Server * server)45 ServerPrivate::ServerPrivate(Server *server)
46 : QObject(server),
47 q(server)
48 {
49 connect(&timer, &QTimer::timeout, this, &ServerPrivate::onTimeout);
50 connect(&ipv4Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead);
51 connect(&ipv6Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead);
52
53 timer.setInterval(60 * 1000);
54 timer.setSingleShot(true);
55 onTimeout();
56 }
57
bindSocket(QUdpSocket & socket,const QHostAddress & address)58 bool ServerPrivate::bindSocket(QUdpSocket &socket, const QHostAddress &address)
59 {
60 // Exit early if the socket is already bound
61 if (socket.state() == QAbstractSocket::BoundState) {
62 return true;
63 }
64
65 // I cannot find the correct combination of flags that allows the socket
66 // to bind properly on Linux, so on that platform, we must manually create
67 // the socket and initialize the QUdpSocket with it
68
69 #ifdef Q_OS_UNIX
70 if (!socket.bind(address, MdnsPort, QAbstractSocket::ShareAddress)) {
71 int arg = 1;
72 if (setsockopt(socket.socketDescriptor(), SOL_SOCKET, SO_REUSEADDR,
73 reinterpret_cast<char*>(&arg), sizeof(int))) {
74 emit q->error(strerror(errno));
75 return false;
76 }
77 #endif
78 if (!socket.bind(address, MdnsPort, QAbstractSocket::ReuseAddressHint)) {
79 emit q->error(socket.errorString());
80 return false;
81 }
82 #ifdef Q_OS_UNIX
83 }
84 #endif
85
86 return true;
87 }
88
onTimeout()89 void ServerPrivate::onTimeout()
90 {
91 // A timer is used to run a set of operations once per minute; first, the
92 // two sockets are bound - if this fails, another attempt is made once per
93 // timeout; secondly, all network interfaces are enumerated; if the
94 // interface supports multicast, the socket will join the mDNS multicast
95 // groups
96
97 bool ipv4Bound = bindSocket(ipv4Socket, QHostAddress::AnyIPv4);
98 bool ipv6Bound = bindSocket(ipv6Socket, QHostAddress::AnyIPv6);
99
100 if (ipv4Bound || ipv6Bound) {
101 foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) {
102 if (interface.flags() & QNetworkInterface::CanMulticast) {
103 if (ipv4Bound) {
104 ipv4Socket.joinMulticastGroup(MdnsIpv4Address, interface);
105 }
106 if (ipv6Bound) {
107 ipv6Socket.joinMulticastGroup(MdnsIpv6Address, interface);
108 }
109 }
110 }
111 }
112
113 timer.start();
114 }
115
onReadyRead()116 void ServerPrivate::onReadyRead()
117 {
118 // Read the packet from the socket
119 QUdpSocket *socket = qobject_cast<QUdpSocket*>(sender());
120 QByteArray packet;
121 packet.resize(socket->pendingDatagramSize());
122 QHostAddress address;
123 quint16 port;
124 socket->readDatagram(packet.data(), packet.size(), &address, &port);
125
126 // Attempt to decode the packet
127 Message message;
128 if (fromPacket(packet, message)) {
129 message.setAddress(address);
130 message.setPort(port);
131 emit q->messageReceived(message);
132 }
133 }
134
Server(QObject * parent)135 Server::Server(QObject *parent)
136 : AbstractServer(parent),
137 d(new ServerPrivate(this))
138 {
139 }
140
sendMessage(const Message & message)141 void Server::sendMessage(const Message &message)
142 {
143 QByteArray packet;
144 toPacket(message, packet);
145 if (message.address().protocol() == QAbstractSocket::IPv4Protocol) {
146 d->ipv4Socket.writeDatagram(packet, message.address(), message.port());
147 } else {
148 d->ipv6Socket.writeDatagram(packet, message.address(), message.port());
149 }
150 }
151
sendMessageToAll(const Message & message)152 void Server::sendMessageToAll(const Message &message)
153 {
154 QByteArray packet;
155 toPacket(message, packet);
156 d->ipv4Socket.writeDatagram(packet, MdnsIpv4Address, MdnsPort);
157 d->ipv6Socket.writeDatagram(packet, MdnsIpv6Address, MdnsPort);
158 }
159