添加广播MDNS服务

This commit is contained in:
2024-08-19 09:39:32 +08:00
parent 1f7bc017ca
commit 0a5b0db9a5
192 changed files with 22181 additions and 616 deletions

View File

@@ -0,0 +1,32 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/abstractserver.h>
using namespace QMdnsEngine;
AbstractServer::AbstractServer(QObject *parent)
: QObject(parent)
{
}

View File

@@ -0,0 +1,108 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/bitmap.h>
#include "bitmap_p.h"
using namespace QMdnsEngine;
BitmapPrivate::BitmapPrivate()
: length(0),
data(nullptr)
{
}
BitmapPrivate::~BitmapPrivate()
{
free();
}
void BitmapPrivate::free()
{
if (data) {
delete[] data;
}
}
void BitmapPrivate::fromData(quint8 newLength, const quint8 *newData)
{
data = new quint8[newLength];
for (int i = 0; i < newLength; ++i) {
data[i] = newData[i];
}
length = newLength;
}
Bitmap::Bitmap()
: d(new BitmapPrivate)
{
}
Bitmap::Bitmap(const Bitmap &other)
: d(new BitmapPrivate)
{
d->fromData(other.d->length, other.d->data);
}
Bitmap &Bitmap::operator=(const Bitmap &other)
{
d->free();
d->fromData(other.d->length, other.d->data);
return *this;
}
bool Bitmap::operator==(const Bitmap &other)
{
if (d->length != other.d->length) {
return false;
}
for (int i = 0; i < d->length; ++i) {
if (d->data[i] != other.d->data[i]) {
return false;
}
}
return true;
}
Bitmap::~Bitmap()
{
delete d;
}
quint8 Bitmap::length() const
{
return d->length;
}
const quint8 *Bitmap::data() const
{
return d->data;
}
void Bitmap::setData(quint8 length, const quint8 *data)
{
d->free();
d->fromData(length, data);
}

View File

@@ -0,0 +1,49 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_BITMAP_P_H
#define QMDNSENGINE_BITMAP_P_H
#include <QtGlobal>
namespace QMdnsEngine
{
class BitmapPrivate
{
public:
BitmapPrivate();
virtual ~BitmapPrivate();
void free();
void fromData(quint8 newLength, const quint8 *newData);
quint8 length;
quint8 *data;
};
}
#endif // QMDNSENGINE_BITMAP_P_H

View File

@@ -0,0 +1,291 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/abstractserver.h>
#include <qmdnsengine/browser.h>
#include <qmdnsengine/cache.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/mdns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/query.h>
#include <qmdnsengine/record.h>
#include "browser_p.h"
using namespace QMdnsEngine;
BrowserPrivate::BrowserPrivate(Browser *browser, AbstractServer *server, const QByteArray &type, Cache *existingCache)
: QObject(browser),
server(server),
type(type),
cache(existingCache ? existingCache : new Cache(this)),
q(browser)
{
connect(server, &AbstractServer::messageReceived, this, &BrowserPrivate::onMessageReceived);
connect(cache, &Cache::shouldQuery, this, &BrowserPrivate::onShouldQuery);
connect(cache, &Cache::recordExpired, this, &BrowserPrivate::onRecordExpired);
connect(&queryTimer, &QTimer::timeout, this, &BrowserPrivate::onQueryTimeout);
connect(&serviceTimer, &QTimer::timeout, this, &BrowserPrivate::onServiceTimeout);
queryTimer.setInterval(60 * 1000);
queryTimer.setSingleShot(true);
serviceTimer.setInterval(100);
serviceTimer.setSingleShot(true);
// Immediately begin browsing for services
onQueryTimeout();
}
// TODO: multiple SRV records not supported
bool BrowserPrivate::updateService(const QByteArray &fqName)
{
// Split the FQDN into service name and type
int index = fqName.indexOf('.');
QByteArray serviceName = fqName.left(index);
QByteArray serviceType = fqName.mid(index + 1);
// Immediately return if a PTR record does not exist
Record ptrRecord;
if (!cache->lookupRecord(serviceType, PTR, ptrRecord)) {
return false;
}
// If a SRV record is missing, query for it (by returning true)
Record srvRecord;
if (!cache->lookupRecord(fqName, SRV, srvRecord)) {
return true;
}
Service service;
service.setName(serviceName);
service.setType(serviceType);
service.setHostname(srvRecord.target());
service.setPort(srvRecord.port());
// If TXT records are available for the service, add their values
QList<Record> txtRecords;
if (cache->lookupRecords(fqName, TXT, txtRecords)) {
QMap<QByteArray, QByteArray> attributes;
for (const Record &record : qAsConst(txtRecords)) {
for (auto i = record.attributes().constBegin();
i != record.attributes().constEnd(); ++i) {
attributes.insert(i.key(), i.value());
}
}
service.setAttributes(attributes);
}
// If the service existed, this is an update; otherwise it is a new
// addition; emit the appropriate signal
if (!services.contains(fqName)) {
emit q->serviceAdded(service);
} else if(services.value(fqName) != service) {
emit q->serviceUpdated(service);
}
services.insert(fqName, service);
hostnames.insert(service.hostname());
return false;
}
void BrowserPrivate::onMessageReceived(const Message &message)
{
if (!message.isResponse()) {
return;
}
const bool any = type == MdnsBrowseType;
// Use a set to track all services that are updated in the message to
// prevent unnecessary queries for SRV and TXT records
QSet<QByteArray> updateNames;
const auto records = message.records();
for (const Record &record : records) {
bool cacheRecord = false;
switch (record.type()) {
case PTR:
if (any && record.name() == MdnsBrowseType) {
ptrTargets.insert(record.target());
serviceTimer.start();
cacheRecord = true;
} else if (any || record.name() == type) {
updateNames.insert(record.target());
cacheRecord = true;
}
break;
case SRV:
case TXT:
if (any || record.name().endsWith("." + type)) {
updateNames.insert(record.name());
cacheRecord = true;
}
break;
}
if (cacheRecord) {
cache->addRecord(record);
}
}
// For each of the services marked to be updated, perform the update and
// make a list of all missing SRV records
QSet<QByteArray> queryNames;
for (const QByteArray &name : qAsConst(updateNames)) {
if (updateService(name)) {
queryNames.insert(name);
}
}
// Cache A / AAAA records after services are processed to ensure hostnames are known
for (const Record &record : records) {
bool cacheRecord = false;
switch (record.type()) {
case A:
case AAAA:
cacheRecord = hostnames.contains(record.name());
break;
}
if (cacheRecord) {
cache->addRecord(record);
}
}
// Build and send a query for all of the SRV and TXT records
if (queryNames.count()) {
Message queryMessage;
for (const QByteArray &name : qAsConst(queryNames)) {
Query query;
query.setName(name);
query.setType(SRV);
queryMessage.addQuery(query);
query.setType(TXT);
queryMessage.addQuery(query);
}
server->sendMessageToAll(queryMessage);
}
}
void BrowserPrivate::onShouldQuery(const Record &record)
{
// Assume that all messages in the cache are still in use (by the browser)
// and attempt to renew them immediately
Query query;
query.setName(record.name());
query.setType(record.type());
Message message;
message.addQuery(query);
server->sendMessageToAll(message);
}
void BrowserPrivate::onRecordExpired(const Record &record)
{
// If the SRV record has expired for a service, then it must be
// removed - TXT records on the other hand, cause an update
QByteArray serviceName;
switch (record.type()) {
case SRV:
serviceName = record.name();
break;
case TXT:
updateService(record.name());
return;
default:
return;
}
Service service = services.value(serviceName);
if (!service.name().isNull()) {
emit q->serviceRemoved(service);
services.remove(serviceName);
updateHostnames();
}
}
void BrowserPrivate::onQueryTimeout()
{
Query query;
query.setName(type);
query.setType(PTR);
Message message;
message.addQuery(query);
// TODO: including too many records could cause problems
// Include PTR records for the target that are already known
QList<Record> records;
if (cache->lookupRecords(query.name(), PTR, records)) {
for (const Record &record : qAsConst(records)) {
message.addRecord(record);
}
}
server->sendMessageToAll(message);
queryTimer.start();
}
void BrowserPrivate::onServiceTimeout()
{
if (ptrTargets.count()) {
Message message;
for (const QByteArray &target : qAsConst(ptrTargets)) {
// Add a query for PTR records
Query query;
query.setName(target);
query.setType(PTR);
message.addQuery(query);
// Include PTR records for the target that are already known
QList<Record> records;
if (cache->lookupRecords(target, PTR, records)) {
for (const Record &record : qAsConst(records)) {
message.addRecord(record);
}
}
}
server->sendMessageToAll(message);
ptrTargets.clear();
}
}
void BrowserPrivate::updateHostnames()
{
hostnames.clear();
for (const auto& service : services) {
hostnames.insert(service.hostname());
}
}
Browser::Browser(AbstractServer *server, const QByteArray &type, Cache *cache, QObject *parent)
: QObject(parent),
d(new BrowserPrivate(this, server, type, cache))
{
}

View File

@@ -0,0 +1,83 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_BROWSER_P_H
#define QMDNSENGINE_BROWSER_P_H
#include <QByteArray>
#include <QMap>
#include <QObject>
#include <QSet>
#include <QTimer>
#include <qmdnsengine/service.h>
namespace QMdnsEngine
{
class AbstractServer;
class Browser;
class Cache;
class Message;
class Record;
class BrowserPrivate : public QObject
{
Q_OBJECT
public:
explicit BrowserPrivate(Browser *browser, AbstractServer *server, const QByteArray &type, Cache *existingCache);
bool updateService(const QByteArray &fqName);
AbstractServer *server;
QByteArray type;
Cache *cache;
QSet<QByteArray> ptrTargets;
QMap<QByteArray, Service> services;
QSet<QByteArray> hostnames;
QTimer queryTimer;
QTimer serviceTimer;
private Q_SLOTS:
void onMessageReceived(const Message &message);
void onShouldQuery(const Record &record);
void onRecordExpired(const Record &record);
void onQueryTimeout();
void onServiceTimeout();
private:
void updateHostnames();
Browser *const q;
};
}
#endif // QMDNSENGINE_BROWSER_P_H

View File

@@ -0,0 +1,173 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QtGlobal>
#if(QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#include <QRandomGenerator>
#define USE_QRANDOMGENERATOR
#endif
#include <qmdnsengine/cache.h>
#include <qmdnsengine/dns.h>
#include "cache_p.h"
using namespace QMdnsEngine;
CachePrivate::CachePrivate(Cache *cache)
: QObject(cache),
q(cache)
{
connect(&timer, &QTimer::timeout, this, &CachePrivate::onTimeout);
timer.setSingleShot(true);
}
void CachePrivate::onTimeout()
{
// Loop through all of the records in the cache, emitting the appropriate
// signal when a trigger has passed, determining when the next trigger
// will occur, and removing records that have expired
QDateTime now = QDateTime::currentDateTime();
QDateTime newNextTrigger;
for (auto i = entries.begin(); i != entries.end();) {
// Loop through the triggers and remove ones that have already
// passed
bool shouldQuery = false;
for (auto j = i->triggers.begin(); j != i->triggers.end();) {
if ((*j) <= now) {
shouldQuery = true;
j = i->triggers.erase(j);
} else {
break;
}
}
// If triggers remain, determine the next earliest one; if none
// remain, the record has expired and should be removed
if (i->triggers.length()) {
if (newNextTrigger.isNull() || i->triggers.at(0) < newNextTrigger) {
newNextTrigger = i->triggers.at(0);
}
if (shouldQuery) {
emit q->shouldQuery(i->record);
}
++i;
} else {
emit q->recordExpired(i->record);
i = entries.erase(i);
}
}
// If newNextTrigger contains a value, it will be the time for the next
// trigger and the timer should be started again
nextTrigger = newNextTrigger;
if (!nextTrigger.isNull()) {
timer.start(now.msecsTo(nextTrigger));
}
}
Cache::Cache(QObject *parent)
: QObject(parent),
d(new CachePrivate(this))
{
}
void Cache::addRecord(const Record &record)
{
// If a record exists that matches, remove it from the cache; if the TTL
// is nonzero, it will be added back to the cache with updated times
for (auto i = d->entries.begin(); i != d->entries.end();) {
if ((record.flushCache() &&
(*i).record.name() == record.name() &&
(*i).record.type() == record.type()) ||
(*i).record == record) {
// If the TTL is set to 0, indicate that the record was removed
if (record.ttl() == 0) {
emit recordExpired((*i).record);
}
i = d->entries.erase(i);
// No need to continue further if the TTL was set to 0
if (record.ttl() == 0) {
return;
}
} else {
++i;
}
}
// Use the current time to calculate the triggers and add a random offset
QDateTime now = QDateTime::currentDateTime();
#ifdef USE_QRANDOMGENERATOR
qint64 random = QRandomGenerator::global()->bounded(20);
#else
qint64 random = qrand() % 20;
#endif
QList<QDateTime> triggers{
now.addMSecs(record.ttl() * 500 + random), // 50%
now.addMSecs(record.ttl() * 850 + random), // 85%
now.addMSecs(record.ttl() * 900 + random), // 90%
now.addMSecs(record.ttl() * 950 + random), // 95%
now.addSecs(record.ttl())
};
// Append the record and its triggers
d->entries.append({record, triggers});
// Check if the new record's first trigger is earlier than the next
// scheduled trigger; if so, restart the timer
if (d->nextTrigger.isNull() || triggers.at(0) < d->nextTrigger) {
d->nextTrigger = triggers.at(0);
d->timer.start(now.msecsTo(d->nextTrigger));
}
}
bool Cache::lookupRecord(const QByteArray &name, quint16 type, Record &record) const
{
QList<Record> records;
if (lookupRecords(name, type, records)) {
record = records.at(0);
return true;
}
return false;
}
bool Cache::lookupRecords(const QByteArray &name, quint16 type, QList<Record> &records) const
{
bool recordsAdded = false;
for (const CachePrivate::Entry &entry : d->entries) {
if ((name.isNull() || entry.record.name() == name) &&
(type == ANY || entry.record.type() == type)) {
records.append(entry.record);
recordsAdded = true;
}
}
return recordsAdded;
}

View File

@@ -0,0 +1,69 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_CACHE_P_H
#define QMDNSENGINE_CACHE_P_H
#include <QDateTime>
#include <QList>
#include <QObject>
#include <QTimer>
#include <qmdnsengine/record.h>
namespace QMdnsEngine
{
class Cache;
class CachePrivate : public QObject
{
Q_OBJECT
public:
struct Entry
{
Record record;
QList<QDateTime> triggers;
};
CachePrivate(Cache *cache);
QTimer timer;
QList<Entry> entries;
QDateTime nextTrigger;
private Q_SLOTS:
void onTimeout();
private:
Cache *const q;
};
}
#endif // QMDNSENGINE_CACHE_P_H

View File

@@ -0,0 +1,376 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QHostAddress>
#include <QtEndian>
#include <qmdnsengine/bitmap.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/query.h>
#include <qmdnsengine/record.h>
namespace QMdnsEngine
{
template<class T>
bool parseInteger(const QByteArray &packet, quint16 &offset, T &value)
{
if (offset + sizeof(T) > static_cast<unsigned int>(packet.length())) {
return false; // out-of-bounds
}
value = qFromBigEndian<T>(reinterpret_cast<const uchar*>(packet.constData() + offset));
offset += sizeof(T);
return true;
}
template<class T>
void writeInteger(QByteArray &packet, quint16 &offset, T value)
{
value = qToBigEndian<T>(value);
packet.append(reinterpret_cast<const char*>(&value), sizeof(T));
offset += sizeof(T);
}
bool parseName(const QByteArray &packet, quint16 &offset, QByteArray &name)
{
quint16 offsetEnd = 0;
quint16 offsetPtr = offset;
forever {
quint8 nBytes;
if (!parseInteger<quint8>(packet, offset, nBytes)) {
return false;
}
if (!nBytes) {
break;
}
switch (nBytes & 0xc0) {
case 0x00:
if (offset + nBytes > packet.length()) {
return false; // length exceeds message
}
name.append(packet.mid(offset, nBytes));
name.append('.');
offset += nBytes;
break;
case 0xc0:
{
quint8 nBytes2;
quint16 newOffset;
if (!parseInteger<quint8>(packet, offset, nBytes2)) {
return false;
}
newOffset = ((nBytes & ~0xc0) << 8) | nBytes2;
if (newOffset >= offsetPtr) {
return false; // prevent infinite loop
}
offsetPtr = newOffset;
if (!offsetEnd) {
offsetEnd = offset;
}
offset = newOffset;
break;
}
default:
return false; // no other types supported
}
}
if (offsetEnd) {
offset = offsetEnd;
}
return true;
}
void writeName(QByteArray &packet, quint16 &offset, const QByteArray &name, QMap<QByteArray, quint16> &nameMap)
{
QByteArray fragment = name;
if (fragment.endsWith('.')) {
fragment.chop(1);
}
while (fragment.length()) {
if (nameMap.contains(fragment)) {
writeInteger<quint16>(packet, offset, nameMap.value(fragment) | 0xc000);
return;
}
nameMap.insert(fragment, offset);
int index = fragment.indexOf('.');
if (index == -1) {
index = fragment.length();
}
writeInteger<quint8>(packet, offset, index);
packet.append(fragment.left(index));
offset += index;
fragment.remove(0, index + 1);
}
writeInteger<quint8>(packet, offset, 0);
}
bool parseRecord(const QByteArray &packet, quint16 &offset, Record &record)
{
QByteArray name;
quint16 type, class_, dataLen;
quint32 ttl;
if (!parseName(packet, offset, name) ||
!parseInteger<quint16>(packet, offset, type) ||
!parseInteger<quint16>(packet, offset, class_) ||
!parseInteger<quint32>(packet, offset, ttl) ||
!parseInteger<quint16>(packet, offset, dataLen)) {
return false;
}
record.setName(name);
record.setType(type);
record.setFlushCache(class_ & 0x8000);
record.setTtl(ttl);
switch (type) {
case A:
{
quint32 ipv4Addr;
if (!parseInteger<quint32>(packet, offset, ipv4Addr)) {
return false;
}
record.setAddress(QHostAddress(ipv4Addr));
break;
}
case AAAA:
{
if (offset + 16 > packet.length()) {
return false;
}
record.setAddress(QHostAddress(
reinterpret_cast<const quint8*>(packet.constData() + offset)
));
offset += 16;
break;
}
case NSEC:
{
QByteArray nextDomainName;
quint8 number;
quint8 length;
if (!parseName(packet, offset, nextDomainName) ||
!parseInteger<quint8>(packet, offset, number) ||
!parseInteger<quint8>(packet, offset, length) ||
number != 0 ||
offset + length > packet.length()) {
return false;
}
Bitmap bitmap;
bitmap.setData(length, reinterpret_cast<const quint8*>(packet.constData() + offset));
record.setNextDomainName(nextDomainName);
record.setBitmap(bitmap);
offset += length;
break;
}
case PTR:
{
QByteArray target;
if (!parseName(packet, offset, target)) {
return false;
}
record.setTarget(target);
break;
}
case SRV:
{
quint16 priority, weight, port;
QByteArray target;
if (!parseInteger<quint16>(packet, offset, priority) ||
!parseInteger<quint16>(packet, offset, weight) ||
!parseInteger<quint16>(packet, offset, port) ||
!parseName(packet, offset, target)) {
return false;
}
record.setPriority(priority);
record.setWeight(weight);
record.setPort(port);
record.setTarget(target);
break;
}
case TXT:
{
quint16 start = offset;
while (offset < start + dataLen) {
quint8 nBytes;
if (!parseInteger<quint8>(packet, offset, nBytes) ||
offset + nBytes > packet.length()) {
return false;
}
if (nBytes == 0) {
break;
}
QByteArray attr(packet.constData() + offset, nBytes);
offset += nBytes;
int splitIndex = attr.indexOf('=');
if (splitIndex == -1) {
record.addAttribute(attr, QByteArray());
} else {
record.addAttribute(attr.left(splitIndex), attr.mid(splitIndex + 1));
}
}
break;
}
default:
offset += dataLen;
break;
}
return true;
}
void writeRecord(QByteArray &packet, quint16 &offset, Record &record, QMap<QByteArray, quint16> &nameMap)
{
writeName(packet, offset, record.name(), nameMap);
writeInteger<quint16>(packet, offset, record.type());
writeInteger<quint16>(packet, offset, record.flushCache() ? 0x8001 : 1);
writeInteger<quint32>(packet, offset, record.ttl());
offset += 2;
QByteArray data;
switch (record.type()) {
case A:
writeInteger<quint32>(data, offset, record.address().toIPv4Address());
break;
case AAAA:
{
Q_IPV6ADDR ipv6Addr = record.address().toIPv6Address();
data.append(reinterpret_cast<const char*>(&ipv6Addr), sizeof(Q_IPV6ADDR));
offset += data.length();
break;
}
case NSEC:
{
quint8 length = record.bitmap().length();
writeName(data, offset, record.nextDomainName(), nameMap);
writeInteger<quint8>(data, offset, 0);
writeInteger<quint8>(data, offset, length);
data.append(reinterpret_cast<const char*>(record.bitmap().data()), length);
offset += length;
break;
}
case PTR:
writeName(data, offset, record.target(), nameMap);
break;
case SRV:
writeInteger<quint16>(data, offset, record.priority());
writeInteger<quint16>(data, offset, record.weight());
writeInteger<quint16>(data, offset, record.port());
writeName(data, offset, record.target(), nameMap);
break;
case TXT:
if (!record.attributes().count()) {
writeInteger<quint8>(data, offset, 0);
break;
}
for (auto i = record.attributes().constBegin(); i != record.attributes().constEnd(); ++i) {
QByteArray entry = i.value().isNull() ? i.key() : i.key() + "=" + i.value();
writeInteger<quint8>(data, offset, entry.length());
data.append(entry);
offset += entry.length();
}
break;
default:
break;
}
offset -= 2;
writeInteger<quint16>(packet, offset, data.length());
packet.append(data);
}
bool fromPacket(const QByteArray &packet, Message &message)
{
quint16 offset = 0;
quint16 transactionId, flags, nQuestion, nAnswer, nAuthority, nAdditional;
if (!parseInteger<quint16>(packet, offset, transactionId) ||
!parseInteger<quint16>(packet, offset, flags) ||
!parseInteger<quint16>(packet, offset, nQuestion) ||
!parseInteger<quint16>(packet, offset, nAnswer) ||
!parseInteger<quint16>(packet, offset, nAuthority) ||
!parseInteger<quint16>(packet, offset, nAdditional)) {
return false;
}
message.setTransactionId(transactionId);
message.setResponse(flags & 0x8400);
message.setTruncated(flags & 0x0200);
for (int i = 0; i < nQuestion; ++i) {
QByteArray name;
quint16 type, class_;
if (!parseName(packet, offset, name) ||
!parseInteger<quint16>(packet, offset, type) ||
!parseInteger<quint16>(packet, offset, class_)) {
return false;
}
Query query;
query.setName(name);
query.setType(type);
query.setUnicastResponse(class_ & 0x8000);
message.addQuery(query);
}
quint16 nRecord = nAnswer + nAuthority + nAdditional;
for (int i = 0; i < nRecord; ++i) {
Record record;
if (!parseRecord(packet, offset, record)) {
return false;
}
message.addRecord(record);
}
return true;
}
void toPacket(const Message &message, QByteArray &packet)
{
quint16 offset = 0;
quint16 flags = (message.isResponse() ? 0x8400 : 0) |
(message.isTruncated() ? 0x200 : 0);
writeInteger<quint16>(packet, offset, message.transactionId());
writeInteger<quint16>(packet, offset, flags);
writeInteger<quint16>(packet, offset, message.queries().length());
writeInteger<quint16>(packet, offset, message.records().length());
writeInteger<quint16>(packet, offset, 0);
writeInteger<quint16>(packet, offset, 0);
QMap<QByteArray, quint16> nameMap;
const auto queries = message.queries();
for (const Query &query : queries) {
writeName(packet, offset, query.name(), nameMap);
writeInteger<quint16>(packet, offset, query.type());
writeInteger<quint16>(packet, offset, query.unicastResponse() ? 0x8001 : 1);
}
const auto records = message.records();
for (Record record : records) {
writeRecord(packet, offset, record, nameMap);
}
}
QString typeName(quint16 type)
{
switch (type) {
case A: return "A";
case AAAA: return "AAAA";
case ANY: return "ANY";
case NSEC: return "NSEC";
case PTR: return "PTR";
case SRV: return "SRV";
case TXT: return "TXT";
default: return "?";
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QHostAddress>
#include <QHostInfo>
#include <QNetworkAddressEntry>
#include <QNetworkInterface>
#include <qmdnsengine/abstractserver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/hostname.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/query.h>
#include <qmdnsengine/record.h>
#include "hostname_p.h"
using namespace QMdnsEngine;
HostnamePrivate::HostnamePrivate(Hostname *hostname, AbstractServer *server)
: QObject(hostname),
server(server),
q(hostname)
{
connect(server, &AbstractServer::messageReceived, this, &HostnamePrivate::onMessageReceived);
connect(&registrationTimer, &QTimer::timeout, this, &HostnamePrivate::onRegistrationTimeout);
connect(&rebroadcastTimer, &QTimer::timeout, this, &HostnamePrivate::onRebroadcastTimeout);
registrationTimer.setInterval(2 * 1000);
registrationTimer.setSingleShot(true);
rebroadcastTimer.setInterval(30 * 60 * 1000);
rebroadcastTimer.setSingleShot(true);
// Immediately assert the hostname
onRebroadcastTimeout();
}
void HostnamePrivate::assertHostname()
{
// Begin with the local hostname and replace any "." with "-" (I'm looking
// at you, macOS)
QByteArray localHostname = QHostInfo::localHostName().toUtf8();
localHostname = localHostname.replace('.', '-');
// If the suffix > 1, then append a "-2", "-3", etc. to the hostname to
// aid in finding one that is unique and not in use
hostname = (hostnameSuffix == 1 ? localHostname:
localHostname + "-" + QByteArray::number(hostnameSuffix)) + ".local.";
// Compose a query for A and AAAA records matching the hostname
Query ipv4Query;
ipv4Query.setName(hostname);
ipv4Query.setType(A);
Query ipv6Query;
ipv6Query.setName(hostname);
ipv6Query.setType(AAAA);
Message message;
message.addQuery(ipv4Query);
message.addQuery(ipv6Query);
server->sendMessageToAll(message);
// If no reply is received after two seconds, the hostname is available
registrationTimer.start();
}
bool HostnamePrivate::generateRecord(const QHostAddress &srcAddress, quint16 type, Record &record)
{
// Attempt to find the interface that corresponds with the provided
// address and determine this device's address from the interface
const auto interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface &networkInterface : interfaces) {
const auto entries = networkInterface.addressEntries();
for (const QNetworkAddressEntry &entry : entries) {
if (srcAddress.isInSubnet(entry.ip(), entry.prefixLength())) {
for (const QNetworkAddressEntry &newEntry : entries) {
QHostAddress address = newEntry.ip();
if ((address.protocol() == QAbstractSocket::IPv4Protocol && type == A) ||
(address.protocol() == QAbstractSocket::IPv6Protocol && type == AAAA)) {
record.setName(hostname);
record.setType(type);
record.setAddress(address);
return true;
}
}
}
}
}
return false;
}
void HostnamePrivate::onMessageReceived(const Message &message)
{
if (message.isResponse()) {
if (hostnameRegistered) {
return;
}
const auto records = message.records();
for (const Record &record : records) {
if ((record.type() == A || record.type() == AAAA) && record.name() == hostname) {
++hostnameSuffix;
assertHostname();
}
}
} else {
if (!hostnameRegistered) {
return;
}
Message reply;
reply.reply(message);
const auto queries = message.queries();
for (const Query &query : queries) {
if ((query.type() == A || query.type() == AAAA) && query.name() == hostname) {
Record record;
if (generateRecord(message.address(), query.type(), record)) {
reply.addRecord(record);
}
}
}
if (reply.records().count()) {
server->sendMessage(reply);
}
}
}
void HostnamePrivate::onRegistrationTimeout()
{
hostnameRegistered = true;
if (hostname != hostnamePrev) {
emit q->hostnameChanged(hostname);
}
// Re-assert the hostname in half an hour
rebroadcastTimer.start();
}
void HostnamePrivate::onRebroadcastTimeout()
{
hostnamePrev = hostname;
hostnameRegistered = false;
hostnameSuffix = 1;
assertHostname();
}
Hostname::Hostname(AbstractServer *server, QObject *parent)
: QObject(parent),
d(new HostnamePrivate(this, server))
{
}
bool Hostname::isRegistered() const
{
return d->hostnameRegistered;
}
QByteArray Hostname::hostname() const
{
return d->hostname;
}

View File

@@ -0,0 +1,75 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_HOSTNAME_P_H
#define QMDNSENGINE_HOSTNAME_P_H
#include <QObject>
#include <QTimer>
class QHostAddress;
namespace QMdnsEngine
{
class AbstractServer;
class Hostname;
class Message;
class Record;
class HostnamePrivate : public QObject
{
Q_OBJECT
public:
HostnamePrivate(Hostname *hostname, AbstractServer *server);
void assertHostname();
bool generateRecord(const QHostAddress &srcAddress, quint16 type, Record &record);
AbstractServer *server;
QByteArray hostnamePrev;
QByteArray hostname;
bool hostnameRegistered;
int hostnameSuffix;
QTimer registrationTimer;
QTimer rebroadcastTimer;
private Q_SLOTS:
void onMessageReceived(const Message &message);
void onRegistrationTimeout();
void onRebroadcastTimeout();
private:
Hostname *const q;
};
}
#endif // QMDNSENGINE_HOSTNAME_P_H

View File

@@ -0,0 +1,35 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/mdns.h>
namespace QMdnsEngine
{
const quint16 MdnsPort = 5353;
const QHostAddress MdnsIpv4Address("224.0.0.251");
const QHostAddress MdnsIpv6Address("ff02::fb");
const QByteArray MdnsBrowseType("_services._dns-sd._udp.local.");
}

View File

@@ -0,0 +1,148 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/mdns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/query.h>
#include <qmdnsengine/record.h>
#include "message_p.h"
using namespace QMdnsEngine;
MessagePrivate::MessagePrivate()
: port(0),
transactionId(0),
isResponse(false),
isTruncated(false)
{
}
Message::Message()
: d(new MessagePrivate)
{
}
Message::Message(const Message &other)
: d(new MessagePrivate)
{
*this = other;
}
Message &Message::operator=(const Message &other)
{
*d = *other.d;
return *this;
}
Message::~Message()
{
delete d;
}
QHostAddress Message::address() const
{
return d->address;
}
void Message::setAddress(const QHostAddress &address)
{
d->address = address;
}
quint16 Message::port() const
{
return d->port;
}
void Message::setPort(quint16 port)
{
d->port = port;
}
quint16 Message::transactionId() const
{
return d->transactionId;
}
void Message::setTransactionId(quint16 transactionId)
{
d->transactionId = transactionId;
}
bool Message::isResponse() const
{
return d->isResponse;
}
void Message::setResponse(bool isResponse)
{
d->isResponse = isResponse;
}
bool Message::isTruncated() const
{
return d->isTruncated;
}
void Message::setTruncated(bool isTruncated)
{
d->isTruncated = isTruncated;
}
QList<Query> Message::queries() const
{
return d->queries;
}
void Message::addQuery(const Query &query)
{
d->queries.append(query);
}
QList<Record> Message::records() const
{
return d->records;
}
void Message::addRecord(const Record &record)
{
d->records.append(record);
}
void Message::reply(const Message &other)
{
if (other.port() == MdnsPort) {
if (other.address().protocol() == QAbstractSocket::IPv4Protocol) {
setAddress(MdnsIpv4Address);
} else {
setAddress(MdnsIpv6Address);
}
} else {
setAddress(other.address());
}
setPort(other.port());
setTransactionId(other.transactionId());
setResponse(true);
}

View File

@@ -0,0 +1,54 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_MESSAGE_P_H
#define QMDNSENGINE_MESSAGE_P_H
#include <QHostAddress>
#include <QList>
namespace QMdnsEngine
{
class Query;
class Record;
class MessagePrivate
{
public:
MessagePrivate();
QHostAddress address;
quint16 port;
quint16 transactionId;
bool isResponse;
bool isTruncated;
QList<Query> queries;
QList<Record> records;
};
}
#endif // QMDNSENGINE_MESSAGE_P_H

View File

@@ -0,0 +1,107 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/abstractserver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/prober.h>
#include <qmdnsengine/query.h>
#include "prober_p.h"
using namespace QMdnsEngine;
ProberPrivate::ProberPrivate(Prober *prober, AbstractServer *server, const Record &record)
: QObject(prober),
server(server),
confirmed(false),
proposedRecord(record),
suffix(1),
q(prober)
{
// All records should contain at least one "."
int index = record.name().indexOf('.');
name = record.name().left(index);
type = record.name().mid(index);
connect(server, &AbstractServer::messageReceived, this, &ProberPrivate::onMessageReceived);
connect(&timer, &QTimer::timeout, this, &ProberPrivate::onTimeout);
timer.setSingleShot(true);
assertRecord();
}
void ProberPrivate::assertRecord()
{
// Use the current suffix to set the name of the proposed record
QString tmpName = suffix == 1
? QString("%1%2").arg(name, type.constData())
: QString("%1-%2%3").arg(name.constData(), QByteArray::number(suffix), type);
proposedRecord.setName(tmpName.toUtf8());
// Broadcast a query for the proposed name (using an ANY query) and
// include the proposed record in the query
Query query;
query.setName(proposedRecord.name());
query.setType(ANY);
Message message;
message.addQuery(query);
message.addRecord(proposedRecord);
server->sendMessageToAll(message);
// Wait two seconds to confirm it is unique
timer.stop();
timer.start(2 * 1000);
}
void ProberPrivate::onMessageReceived(const Message &message)
{
// If the response matches the proposed record, increment the suffix and
// try with the new name
if (confirmed || !message.isResponse()) {
return;
}
const auto records = message.records();
for (const Record &record : records) {
if (record.name() == proposedRecord.name() && record.type() == proposedRecord.type()) {
++suffix;
assertRecord();
}
}
}
void ProberPrivate::onTimeout()
{
confirmed = true;
emit q->nameConfirmed(proposedRecord.name());
}
Prober::Prober(AbstractServer *server, const Record &record, QObject *parent)
: QObject(parent),
d(new ProberPrivate(this, server, record))
{
}

View File

@@ -0,0 +1,72 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_PROBER_P_H
#define QMDNSENGINE_PROBER_P_H
#include <QObject>
#include <QTimer>
#include <qmdnsengine/record.h>
namespace QMdnsEngine
{
class AbstractServer;
class Message;
class Prober;
class ProberPrivate : public QObject
{
Q_OBJECT
public:
ProberPrivate(Prober *prober, AbstractServer *server, const Record &record);
void assertRecord();
AbstractServer *server;
QTimer timer;
bool confirmed;
Record proposedRecord;
QByteArray name;
QByteArray type;
int suffix;
private Q_SLOTS:
void onMessageReceived(const Message &message);
void onTimeout();
private:
Prober *const q;
};
}
#endif // QMDNSENGINE_PROBER_P_H

View File

@@ -0,0 +1,235 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/abstractserver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/hostname.h>
#include <qmdnsengine/mdns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/prober.h>
#include <qmdnsengine/provider.h>
#include <qmdnsengine/query.h>
#include "provider_p.h"
using namespace QMdnsEngine;
ProviderPrivate::ProviderPrivate(QObject *parent, AbstractServer *server, Hostname *hostname)
: QObject(parent),
server(server),
hostname(hostname),
prober(nullptr),
initialized(false),
confirmed(false)
{
connect(server, &AbstractServer::messageReceived, this, &ProviderPrivate::onMessageReceived);
connect(hostname, &Hostname::hostnameChanged, this, &ProviderPrivate::onHostnameChanged);
browsePtrProposed.setName(MdnsBrowseType);
browsePtrProposed.setType(PTR);
ptrProposed.setType(PTR);
srvProposed.setType(SRV);
txtProposed.setType(TXT);
}
ProviderPrivate::~ProviderPrivate()
{
if (confirmed) {
farewell();
}
}
void ProviderPrivate::announce()
{
// Broadcast a message with each of the records
Message message;
message.setResponse(true);
message.addRecord(ptrRecord);
message.addRecord(srvRecord);
message.addRecord(txtRecord);
server->sendMessageToAll(message);
}
void ProviderPrivate::confirm()
{
// Confirm that the desired name is unique through probing
if (prober) {
delete prober;
}
prober = new Prober(server, srvProposed, this);
connect(prober, &Prober::nameConfirmed, [this](const QByteArray &name) {
// If existing records were confirmed, indicate that they are no
// longer valid
if (confirmed) {
farewell();
} else {
confirmed = true;
}
// Update the proposed records
ptrProposed.setTarget(name);
srvProposed.setName(name);
txtProposed.setName(name);
// Publish the proposed records and announce them
publish();
delete prober;
prober = nullptr;
});
}
void ProviderPrivate::farewell()
{
// Send a message indicating that the existing records are no longer valid
// by setting their TTL to 0
ptrRecord.setTtl(0);
srvRecord.setTtl(0);
txtRecord.setTtl(0);
announce();
}
void ProviderPrivate::publish()
{
// Copy the proposed records over and announce them
browsePtrRecord = browsePtrProposed;
ptrRecord = ptrProposed;
srvRecord = srvProposed;
txtRecord = txtProposed;
announce();
}
void ProviderPrivate::onMessageReceived(const Message &message)
{
if (!confirmed || message.isResponse()) {
return;
}
bool sendBrowsePtr = false;
bool sendPtr = false;
bool sendSrv = false;
bool sendTxt = false;
// Determine which records to send based on the queries
const auto queries = message.queries();
for (const Query &query : queries) {
if (query.type() == PTR && query.name() == MdnsBrowseType) {
sendBrowsePtr = true;
} else if (query.type() == PTR && query.name() == ptrRecord.name()) {
sendPtr = true;
} else if (query.type() == SRV && query.name() == srvRecord.name()) {
sendSrv = true;
} else if (query.type() == TXT && query.name() == txtRecord.name()) {
sendTxt = true;
}
}
// Remove records to send if they are already known
const auto records = message.records();
for (const Record &record : records) {
if (record == ptrRecord) {
sendPtr = false;
} else if (record == srvRecord) {
sendSrv = false;
} else if (record == txtRecord) {
sendTxt = false;
}
}
// Include the SRV and TXT if the PTR is being sent
if (sendPtr) {
sendSrv = sendTxt = true;
}
// If any records should be sent, compose a message reply
if (sendBrowsePtr || sendPtr || sendSrv || sendTxt) {
Message reply;
reply.reply(message);
if (sendBrowsePtr) {
reply.addRecord(browsePtrRecord);
}
if (sendPtr) {
reply.addRecord(ptrRecord);
}
if (sendSrv) {
reply.addRecord(srvRecord);
}
if (sendTxt) {
reply.addRecord(txtRecord);
}
server->sendMessage(reply);
}
}
void ProviderPrivate::onHostnameChanged(const QByteArray &newHostname)
{
// Update the proposed SRV record
srvProposed.setTarget(newHostname);
// If initialized, confirm the record
if (initialized) {
confirm();
}
}
Provider::Provider(AbstractServer *server, Hostname *hostname, QObject *parent)
: QObject(parent),
d(new ProviderPrivate(this, server, hostname))
{
}
void Provider::update(const Service &service)
{
d->initialized = true;
// Clean the service name
QByteArray serviceName = service.name();
serviceName = serviceName.replace('.', '-');
// Update the proposed records
QByteArray fqName = serviceName + "." + service.type();
d->browsePtrProposed.setTarget(service.type());
d->ptrProposed.setName(service.type());
d->ptrProposed.setTarget(fqName);
d->srvProposed.setName(fqName);
d->srvProposed.setPort(service.port());
d->srvProposed.setTarget(d->hostname->hostname());
d->txtProposed.setName(fqName);
d->txtProposed.setAttributes(service.attributes());
// Assuming a valid hostname exists, check to see if the new service uses
// a different name - if so, it must first be confirmed
if (d->hostname->isRegistered()) {
if (!d->confirmed || fqName != d->srvRecord.name()) {
d->confirm();
} else {
d->publish();
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_PROVIDER_P_H
#define QMDNSENGINE_PROVIDER_P_H
#include <QObject>
#include <qmdnsengine/record.h>
#include <qmdnsengine/service.h>
namespace QMdnsEngine
{
class AbstractServer;
class Hostname;
class Message;
class Prober;
class ProviderPrivate : public QObject
{
Q_OBJECT
public:
ProviderPrivate(QObject *parent, AbstractServer *server, Hostname *hostname);
virtual ~ProviderPrivate();
void announce();
void confirm();
void farewell();
void publish();
AbstractServer *server;
Hostname *hostname;
Prober *prober;
Service service;
bool initialized;
bool confirmed;
Record browsePtrRecord;
Record ptrRecord;
Record srvRecord;
Record txtRecord;
Record browsePtrProposed;
Record ptrProposed;
Record srvProposed;
Record txtProposed;
private Q_SLOTS:
void onMessageReceived(const Message &message);
void onHostnameChanged(const QByteArray &hostname);
};
}
#endif // QMDNSENGINE_PROVIDER_P_H

View File

@@ -0,0 +1,100 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QDebug>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/query.h>
#include "query_p.h"
using namespace QMdnsEngine;
QueryPrivate::QueryPrivate()
: type(0),
unicastResponse(false)
{
}
Query::Query()
: d(new QueryPrivate)
{
}
Query::Query(const Query &other)
: d(new QueryPrivate)
{
*this = other;
}
Query &Query::operator=(const Query &other)
{
*d = *other.d;
return *this;
}
Query::~Query()
{
delete d;
}
QByteArray Query::name() const
{
return d->name;
}
void Query::setName(const QByteArray &name)
{
d->name = name;
}
quint16 Query::type() const
{
return d->type;
}
void Query::setType(quint16 type)
{
d->type = type;
}
bool Query::unicastResponse() const
{
return d->unicastResponse;
}
void Query::setUnicastResponse(bool unicastResponse)
{
d->unicastResponse = unicastResponse;
}
QDebug QMdnsEngine::operator<<(QDebug dbg, const Query &query)
{
QDebugStateSaver saver(dbg);
Q_UNUSED(saver);
dbg.noquote().nospace() << "Query(" << typeName(query.type()) << " " << query.name() << ")";
return dbg;
}

View File

@@ -0,0 +1,46 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_QUERY_P_H
#define QMDNSENGINE_QUERY_P_H
#include <QByteArray>
namespace QMdnsEngine
{
class QueryPrivate
{
public:
QueryPrivate();
QByteArray name;
quint16 type;
bool unicastResponse;
};
}
#endif // QMDNSENGINE_QUERY_P_H

View File

@@ -0,0 +1,218 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QDebug>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/record.h>
#include "record_p.h"
using namespace QMdnsEngine;
RecordPrivate::RecordPrivate()
: type(0),
flushCache(false),
ttl(3600),
priority(0),
weight(0),
port(0)
{
}
Record::Record()
: d(new RecordPrivate)
{
}
Record::Record(const Record &other)
: d(new RecordPrivate)
{
*this = other;
}
Record &Record::operator=(const Record &other)
{
*d = *other.d;
return *this;
}
bool Record::operator==(const Record &other) const
{
return d->name == other.d->name &&
d->type == other.d->type &&
d->address == other.d->address &&
d->target == other.d->target &&
d->nextDomainName == other.d->nextDomainName &&
d->priority == other.d->priority &&
d->weight == other.d->weight &&
d->port == other.d->port &&
d->attributes == other.d->attributes &&
d->bitmap == other.d->bitmap;
}
bool Record::operator!=(const Record &other) const
{
return !(*this == other);
}
Record::~Record()
{
delete d;
}
QByteArray Record::name() const
{
return d->name;
}
void Record::setName(const QByteArray &name)
{
d->name = name;
}
quint16 Record::type() const
{
return d->type;
}
void Record::setType(quint16 type)
{
d->type = type;
}
bool Record::flushCache() const
{
return d->flushCache;
}
void Record::setFlushCache(bool flushCache)
{
d->flushCache = flushCache;
}
quint32 Record::ttl() const
{
return d->ttl;
}
void Record::setTtl(quint32 ttl)
{
d->ttl = ttl;
}
QHostAddress Record::address() const
{
return d->address;
}
void Record::setAddress(const QHostAddress &address)
{
d->address = address;
}
QByteArray Record::target() const
{
return d->target;
}
void Record::setTarget(const QByteArray &target)
{
d->target = target;
}
QByteArray Record::nextDomainName() const
{
return d->nextDomainName;
}
void Record::setNextDomainName(const QByteArray &nextDomainName)
{
d->nextDomainName = nextDomainName;
}
quint16 Record::priority() const
{
return d->priority;
}
void Record::setPriority(quint16 priority)
{
d->priority = priority;
}
quint16 Record::weight() const
{
return d->weight;
}
void Record::setWeight(quint16 weight)
{
d->weight = weight;
}
quint16 Record::port() const
{
return d->port;
}
void Record::setPort(quint16 port)
{
d->port = port;
}
QMap<QByteArray, QByteArray> Record::attributes() const
{
return d->attributes;
}
void Record::setAttributes(const QMap<QByteArray, QByteArray> &attributes)
{
d->attributes = attributes;
}
void Record::addAttribute(const QByteArray &key, const QByteArray &value)
{
d->attributes.insert(key, value);
}
Bitmap Record::bitmap() const
{
return d->bitmap;
}
void Record::setBitmap(const Bitmap &bitmap)
{
d->bitmap = bitmap;
}
QDebug QMdnsEngine::operator<<(QDebug dbg, const Record &record)
{
QDebugStateSaver saver(dbg);
Q_UNUSED(saver);
dbg.noquote().nospace() << "Record(" << typeName(record.type()) << " " << record.name() << ")";
return dbg;
}

View File

@@ -0,0 +1,59 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_RECORD_P_H
#define QMDNSENGINE_RECORD_P_H
#include <QByteArray>
#include <QHostAddress>
#include <QMap>
#include <qmdnsengine/bitmap.h>
namespace QMdnsEngine {
class RecordPrivate
{
public:
RecordPrivate();
QByteArray name;
quint16 type;
bool flushCache;
quint32 ttl;
QHostAddress address;
QByteArray target;
QByteArray nextDomainName;
quint16 priority;
quint16 weight;
quint16 port;
QMap<QByteArray, QByteArray> attributes;
Bitmap bitmap;
};
}
#endif // QMDNSENGINE_RECORD_P_H

View File

@@ -0,0 +1,116 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QTimer>
#include <qmdnsengine/abstractserver.h>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/cache.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/query.h>
#include <qmdnsengine/record.h>
#include <qmdnsengine/resolver.h>
#include "resolver_p.h"
using namespace QMdnsEngine;
ResolverPrivate::ResolverPrivate(Resolver *resolver, AbstractServer *server, const QByteArray &name, Cache *cache)
: QObject(resolver),
server(server),
name(name),
cache(cache ? cache : new Cache(this)),
q(resolver)
{
connect(server, &AbstractServer::messageReceived, this, &ResolverPrivate::onMessageReceived);
connect(&timer, &QTimer::timeout, this, &ResolverPrivate::onTimeout);
// Query for new records
query();
// Pull the existing records from the cache
timer.setSingleShot(true);
timer.start(0);
}
QList<Record> ResolverPrivate::existing() const
{
QList<Record> records;
cache->lookupRecords(name, A, records);
cache->lookupRecords(name, AAAA, records);
return records;
}
void ResolverPrivate::query() const
{
Message message;
// Add a query for A and AAAA records
Query query;
query.setName(name);
query.setType(A);
message.addQuery(query);
query.setType(AAAA);
message.addQuery(query);
// Add existing (known) records to the query
const auto records = existing();
for (const Record &record : records) {
message.addRecord(record);
}
// Send the query
server->sendMessageToAll(message);
}
void ResolverPrivate::onMessageReceived(const Message &message)
{
if (!message.isResponse()) {
return;
}
const auto records = message.records();
for (const Record &record : records) {
if (record.name() == name && (record.type() == A || record.type() == AAAA)) {
cache->addRecord(record);
if (!addresses.contains(record.address())) {
emit q->resolved(record.address());
addresses.insert(record.address());
}
}
}
}
void ResolverPrivate::onTimeout()
{
const auto records = existing();
for (const Record &record : records) {
emit q->resolved(record.address());
}
}
Resolver::Resolver(AbstractServer *server, const QByteArray &name, Cache *cache, QObject *parent)
: QObject(parent),
d(new ResolverPrivate(this, server, name, cache))
{
}

View File

@@ -0,0 +1,71 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_RESOLVER_P_H
#define QMDNSENGINE_RESOLVER_P_H
#include <QHostAddress>
#include <QObject>
#include <QSet>
#include <QTimer>
namespace QMdnsEngine
{
class AbstractServer;
class Cache;
class Message;
class Record;
class Resolver;
class ResolverPrivate : public QObject
{
Q_OBJECT
public:
explicit ResolverPrivate(Resolver *resolver, AbstractServer *server, const QByteArray &name, Cache *cache);
QList<Record> existing() const;
void query() const;
AbstractServer *server;
QByteArray name;
Cache *cache;
QSet<QHostAddress> addresses;
QTimer timer;
private Q_SLOTS:
void onMessageReceived(const Message &message);
void onTimeout();
private:
Resolver *const q;
};
}
#endif // QMDNSENGINE_RESOLVER_P_H

View File

@@ -0,0 +1,159 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <QtGlobal>
#ifdef Q_OS_UNIX
# include <cerrno>
# include <cstring>
# include <sys/socket.h>
#endif
#include <QHostAddress>
#include <QNetworkInterface>
#include <qmdnsengine/dns.h>
#include <qmdnsengine/mdns.h>
#include <qmdnsengine/message.h>
#include <qmdnsengine/server.h>
#include "server_p.h"
using namespace QMdnsEngine;
ServerPrivate::ServerPrivate(Server *server)
: QObject(server),
q(server)
{
connect(&timer, &QTimer::timeout, this, &ServerPrivate::onTimeout);
connect(&ipv4Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead);
connect(&ipv6Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead);
timer.setInterval(60 * 1000);
timer.setSingleShot(true);
onTimeout();
}
bool ServerPrivate::bindSocket(QUdpSocket &socket, const QHostAddress &address)
{
// Exit early if the socket is already bound
if (socket.state() == QAbstractSocket::BoundState) {
return true;
}
// I cannot find the correct combination of flags that allows the socket
// to bind properly on Linux, so on that platform, we must manually create
// the socket and initialize the QUdpSocket with it
#ifdef Q_OS_UNIX
if (!socket.bind(address, MdnsPort, QAbstractSocket::ShareAddress)) {
int arg = 1;
if (setsockopt(socket.socketDescriptor(), SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<char*>(&arg), sizeof(int))) {
emit q->error(strerror(errno));
return false;
}
#endif
if (!socket.bind(address, MdnsPort, QAbstractSocket::ReuseAddressHint)) {
emit q->error(socket.errorString());
return false;
}
#ifdef Q_OS_UNIX
}
#endif
return true;
}
void ServerPrivate::onTimeout()
{
// A timer is used to run a set of operations once per minute; first, the
// two sockets are bound - if this fails, another attempt is made once per
// timeout; secondly, all network interfaces are enumerated; if the
// interface supports multicast, the socket will join the mDNS multicast
// groups
bool ipv4Bound = bindSocket(ipv4Socket, QHostAddress::AnyIPv4);
bool ipv6Bound = bindSocket(ipv6Socket, QHostAddress::AnyIPv6);
if (ipv4Bound || ipv6Bound) {
const auto interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface &networkInterface : interfaces) {
if (networkInterface.flags() & QNetworkInterface::CanMulticast) {
if (ipv4Bound) {
ipv4Socket.joinMulticastGroup(MdnsIpv4Address, networkInterface);
}
if (ipv6Bound) {
ipv6Socket.joinMulticastGroup(MdnsIpv6Address, networkInterface);
}
}
}
}
timer.start();
}
void ServerPrivate::onReadyRead()
{
// Read the packet from the socket
QUdpSocket *socket = qobject_cast<QUdpSocket*>(sender());
QByteArray packet;
packet.resize(socket->pendingDatagramSize());
QHostAddress address;
quint16 port;
socket->readDatagram(packet.data(), packet.size(), &address, &port);
// Attempt to decode the packet
Message message;
if (fromPacket(packet, message)) {
message.setAddress(address);
message.setPort(port);
emit q->messageReceived(message);
}
}
Server::Server(QObject *parent)
: AbstractServer(parent),
d(new ServerPrivate(this))
{
}
void Server::sendMessage(const Message &message)
{
QByteArray packet;
toPacket(message, packet);
if (message.address().protocol() == QAbstractSocket::IPv4Protocol) {
d->ipv4Socket.writeDatagram(packet, message.address(), message.port());
} else {
d->ipv6Socket.writeDatagram(packet, message.address(), message.port());
}
}
void Server::sendMessageToAll(const Message &message)
{
QByteArray packet;
toPacket(message, packet);
d->ipv4Socket.writeDatagram(packet, MdnsIpv4Address, MdnsPort);
d->ipv6Socket.writeDatagram(packet, MdnsIpv6Address, MdnsPort);
}

View File

@@ -0,0 +1,65 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_SERVER_P_H
#define QMDNSENGINE_SERVER_P_H
#include <QObject>
#include <QTimer>
#include <QUdpSocket>
class QHostAddress;
namespace QMdnsEngine
{
class Server;
class ServerPrivate : public QObject
{
Q_OBJECT
public:
explicit ServerPrivate(Server *server);
bool bindSocket(QUdpSocket &socket, const QHostAddress &address);
QTimer timer;
QUdpSocket ipv4Socket;
QUdpSocket ipv6Socket;
private Q_SLOTS:
void onTimeout();
void onReadyRead();
private:
Server *const q;
};
}
#endif // QMDNSENGINE_SERVER_P_H

View File

@@ -0,0 +1,139 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <qmdnsengine/service.h>
#include "service_p.h"
using namespace QMdnsEngine;
ServicePrivate::ServicePrivate()
{
}
Service::Service()
: d(new ServicePrivate)
{
}
Service::Service(const Service &other)
: d(new ServicePrivate)
{
*this = other;
}
Service &Service::operator=(const Service &other)
{
*d = *other.d;
return *this;
}
bool Service::operator==(const Service &other) const
{
return d->type == other.d->type &&
d->name == other.d->name &&
d->port == other.d->port &&
d->attributes == other.d->attributes;
}
bool Service::operator!=(const Service &other) const
{
return !(*this == other);
}
Service::~Service()
{
delete d;
}
QByteArray Service::type() const
{
return d->type;
}
void Service::setType(const QByteArray &type)
{
d->type = type;
}
QByteArray Service::name() const
{
return d->name;
}
void Service::setName(const QByteArray &name)
{
d->name = name;
}
QByteArray Service::hostname() const
{
return d->hostname;
}
void Service::setHostname(const QByteArray &hostname)
{
d->hostname = hostname;
}
quint16 Service::port() const
{
return d->port;
}
void Service::setPort(quint16 port)
{
d->port = port;
}
QMap<QByteArray, QByteArray> Service::attributes() const
{
return d->attributes;
}
void Service::setAttributes(const QMap<QByteArray, QByteArray> &attributes)
{
d->attributes = attributes;
}
void Service::addAttribute(const QByteArray &key, const QByteArray &value)
{
d->attributes.insert(key, value);
}
QDebug QMdnsEngine::operator<<(QDebug debug, const Service &service)
{
QDebugStateSaver saver(debug);
Q_UNUSED(saver);
debug.noquote().nospace()
<< "Service(name: " << service.name()
<< ", type: " << service.type()
<< ", hostname: " << service.hostname()
<< ", port: " << service.port()
<< ", attributes: " << service.attributes()
<< ")";
return debug;
}

View File

@@ -0,0 +1,49 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef QMDNSENGINE_SERVICE_P_H
#define QMDNSENGINE_SERVICE_P_H
#include <QByteArray>
#include <QMap>
namespace QMdnsEngine
{
class ServicePrivate
{
public:
ServicePrivate();
QByteArray type;
QByteArray name;
QByteArray hostname;
quint16 port;
QMap<QByteArray, QByteArray> attributes;
};
}
#endif // QMDNSENGINE_SERVICE_P_H