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 <QHostAddress>
26 #include <QHostInfo>
27 #include <QNetworkAddressEntry>
28 #include <QNetworkInterface>
29 
30 #include <qmdnsengine/abstractserver.h>
31 #include <qmdnsengine/dns.h>
32 #include <qmdnsengine/hostname.h>
33 #include <qmdnsengine/message.h>
34 #include <qmdnsengine/query.h>
35 #include <qmdnsengine/record.h>
36 
37 #include "hostname_p.h"
38 
39 using namespace QMdnsEngine;
40 
HostnamePrivate(Hostname * hostname,AbstractServer * server)41 HostnamePrivate::HostnamePrivate(Hostname *hostname, AbstractServer *server)
42     : QObject(hostname),
43       q(hostname),
44       server(server)
45 {
46     connect(server, &AbstractServer::messageReceived, this, &HostnamePrivate::onMessageReceived);
47     connect(&registrationTimer, &QTimer::timeout, this, &HostnamePrivate::onRegistrationTimeout);
48     connect(&rebroadcastTimer, &QTimer::timeout, this, &HostnamePrivate::onRebroadcastTimeout);
49 
50     registrationTimer.setSingleShot(true);
51     rebroadcastTimer.setSingleShot(true);
52 
53     // Immediately assert the hostname
54     onRebroadcastTimeout();
55 }
56 
resetHostname()57 void HostnamePrivate::resetHostname()
58 {
59     hostnamePrev = hostname;
60     hostnameRegistered = false;
61     hostnameSuffix = 1;
62 }
63 
assertHostname()64 void HostnamePrivate::assertHostname()
65 {
66     // Begin with the local hostname and replace any "." with "-" (I'm looking
67     // at you, macOS)
68     QByteArray localHostname = QHostInfo::localHostName().toUtf8();
69     localHostname = localHostname.replace('.', '-');
70 
71     // If the suffix > 1, then append a "-2", "-3", etc. to the hostname to
72     // aid in finding one that is unique and not in use
73     hostname = (hostnameSuffix == 1 ? localHostname:
74         localHostname + "-" + QByteArray::number(hostnameSuffix)) + ".local.";
75 
76     // Compose a query for A and AAAA records matching the hostname
77     Query ipv4Query;
78     ipv4Query.setName(hostname);
79     ipv4Query.setType(A);
80     Query ipv6Query;
81     ipv6Query.setName(hostname);
82     ipv6Query.setType(AAAA);
83     Message message;
84     message.addQuery(ipv4Query);
85     message.addQuery(ipv6Query);
86 
87     server->sendMessageToAll(message);
88 
89     // If no reply is received after two seconds, the hostname is available
90     registrationTimer.stop();
91     registrationTimer.start(2 * 1000);
92 }
93 
generateRecord(const QHostAddress & srcAddress,quint16 type,Record & record)94 bool HostnamePrivate::generateRecord(const QHostAddress &srcAddress, quint16 type, Record &record)
95 {
96     // Attempt to find the interface that corresponds with the provided
97     // address and determine this device's address from the interface
98 
99     foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) {
100         foreach (QNetworkAddressEntry entry, interface.addressEntries()) {
101             if (srcAddress.isInSubnet(entry.ip(), entry.prefixLength())) {
102                 foreach (entry, interface.addressEntries()) {
103                     QHostAddress address = entry.ip();
104                     if ((address.protocol() == QAbstractSocket::IPv4Protocol && type == A) ||
105                             (address.protocol() == QAbstractSocket::IPv6Protocol && type == AAAA)) {
106                         record.setName(hostname);
107                         record.setType(type);
108                         record.setAddress(address);
109                         return true;
110                     }
111                 }
112             }
113         }
114     }
115     return false;
116 }
117 
onMessageReceived(const Message & message)118 void HostnamePrivate::onMessageReceived(const Message &message)
119 {
120     if (message.isResponse()) {
121         if (hostnameRegistered) {
122             return;
123         }
124         foreach (Record record, message.records()) {
125             if ((record.type() == A || record.type() == AAAA) && record.name() == hostname) {
126                 ++hostnameSuffix;
127                 assertHostname();
128             }
129         }
130     } else {
131         if (!hostnameRegistered) {
132             return;
133         }
134         Message reply;
135         reply.reply(message);
136         foreach (Query query, message.queries()) {
137             if ((query.type() == A || query.type() == AAAA) && query.name() == hostname) {
138                 Record record;
139                 if (generateRecord(message.address(), query.type(), record)) {
140                     reply.addRecord(record);
141                 }
142             }
143         }
144         if (reply.records().count()) {
145             server->sendMessage(reply);
146         }
147     }
148 }
149 
onRegistrationTimeout()150 void HostnamePrivate::onRegistrationTimeout()
151 {
152     hostnameRegistered = true;
153     if (hostname != hostnamePrev) {
154         emit q->hostnameChanged(hostname);
155     }
156 
157     // Re-assert the hostname in half an hour
158     rebroadcastTimer.start(30 * 60 * 1000);
159 }
160 
onRebroadcastTimeout()161 void HostnamePrivate::onRebroadcastTimeout()
162 {
163     resetHostname();
164     assertHostname();
165 }
166 
Hostname(AbstractServer * server,QObject * parent)167 Hostname::Hostname(AbstractServer *server, QObject *parent)
168     : QObject(parent),
169       d(new HostnamePrivate(this, server))
170 {
171 }
172 
isRegistered() const173 bool Hostname::isRegistered() const
174 {
175     return d->hostnameRegistered;
176 }
177 
hostname() const178 QByteArray Hostname::hostname() const
179 {
180     return d->hostname;
181 }
182