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