Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling multiple records with flush cache set #15

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/include/qmdnsengine/cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ class QMDNSENGINE_EXPORT Cache : public QObject
* @param record add this record to the cache
*
* The TTL for the record will be added to the current time to calculate
* when the record expires. Existing records of the same name and type
* will be replaced, resetting their expiration.
* when the record expires. Call invalidateRecord() before addRecord()
* to ensure any superseded records are removed.
*/
void addRecord(const Record &record);

Expand All @@ -105,6 +105,15 @@ class QMDNSENGINE_EXPORT Cache : public QObject
*/
bool lookupRecords(const QByteArray &name, quint16 type, QList<Record> &records) const;

/**
* @brief Invalidates the specified record in the cache
* @param record invalidate this record in the cache
*
* This must be called for all records in a message prior to adding
* any records to the cache to ensure the case of multiple records with
* the same type and 'flush cache' set is handled properly.
*/
void invalidateRecord(const Record &record);
Q_SIGNALS:

/**
Expand Down
7 changes: 7 additions & 0 deletions src/src/browser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ void BrowserPrivate::onMessageReceived(const Message &message)
return;
}

// Invalidate each record in the cache first. This ensures
// that we properly handle the case where we have multiple
// records of the same type with 'flush cache' set.
foreach (Record record, message.records()) {
cache->invalidateRecord(record);
}

// 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;
Expand Down
46 changes: 24 additions & 22 deletions src/src/cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,28 +92,9 @@ Cache::Cache(QObject *parent)

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;
}
// No need to add anything if the TTL was set to 0
if (record.ttl() == 0) {
return;
}

// Use the current time to calculate the triggers and add a random offset
Expand All @@ -139,6 +120,27 @@ void Cache::addRecord(const Record &record)
}
}

void Cache::invalidateRecord(const Record &record)
{
// If a record exists that matches, remove it from the cache
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);
} else {
++i;
}
}
}

bool Cache::lookupRecord(const QByteArray &name, quint16 type, Record &record) const
{
QList<Record> records;
Expand Down
8 changes: 8 additions & 0 deletions src/src/resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ void ResolverPrivate::onMessageReceived(const Message &message)
if (!message.isResponse()) {
return;
}

// Invalidate each record in the cache first. This ensures
// that we properly handle the case where we have multiple
// records of the same type with 'flush cache' set.
foreach (Record record, message.records()) {
cache->invalidateRecord(record);
}

foreach (Record record, message.records()) {
if (record.name() == name && (record.type() == A || record.type() == AAAA)) {
cache->addRecord(record);
Expand Down
2 changes: 2 additions & 0 deletions tests/TestCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void TestCache::testRemoval()

// Purge the record from the cache by setting its TTL to 0
record.setTtl(0);
cache.invalidateRecord(record);
cache.addRecord(record);

// Verify that the record is gone
Expand All @@ -115,6 +116,7 @@ void TestCache::testCacheFlush()
// Insert a new record with the cache clear bit set
QMdnsEngine::Record record = createRecord();
record.setFlushCache(true);
cache.invalidateRecord(record);
cache.addRecord(record);

// Confirm that only a single record exists
Expand Down
34 changes: 34 additions & 0 deletions tests/TestResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Q_DECLARE_METATYPE(QHostAddress)

const QByteArray Name = "test.localhost.";
const QHostAddress Address("127.0.0.1");
const QHostAddress Address2("127.0.0.2");

class TestResolver : public QObject
{
Expand All @@ -47,6 +48,7 @@ private Q_SLOTS:

void initTestCase();
void testResolver();
void testResolverCacheFlush();
};

void TestResolver::initTestCase()
Expand Down Expand Up @@ -80,5 +82,37 @@ void TestResolver::testResolver()
QCOMPARE(resolvedSpy.at(0).at(0).value<QHostAddress>(), Address);
}

void TestResolver::testResolverCacheFlush()
{
TestServer server;
QMdnsEngine::Resolver resolver(&server, Name);
QSignalSpy resolvedSpy(&resolver, SIGNAL(resolved(QHostAddress)));

// Ensure two queries were dispatched
QTRY_VERIFY(queryReceived(&server, Name, QMdnsEngine::A));
QVERIFY(queryReceived(&server, Name, QMdnsEngine::AAAA));

// Send a response with 2 flush cache records
QMdnsEngine::Message message;
message.setResponse(true);

QMdnsEngine::Record record;
record.setName(Name);
record.setType(QMdnsEngine::A);
record.setFlushCache(true);
record.setAddress(Address);
message.addRecord(record);

record.setAddress(Address2);
message.addRecord(record);

server.deliverMessage(message);

// Ensure resolved() was emitted with both addresses
QTRY_COMPARE(resolvedSpy.count(), 2);
QCOMPARE(resolvedSpy.at(0).at(0).value<QHostAddress>(), Address);
QCOMPARE(resolvedSpy.at(1).at(0).value<QHostAddress>(), Address2);
}

QTEST_MAIN(TestResolver)
#include "TestResolver.moc"