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(®istrationTimer, &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