diff options
-rw-r--r-- | net/dns/mdns_cache.cc | 22 | ||||
-rw-r--r-- | net/dns/mdns_cache_unittest.cc | 66 | ||||
-rw-r--r-- | net/dns/mdns_client_unittest.cc | 55 |
3 files changed, 133 insertions, 10 deletions
diff --git a/net/dns/mdns_cache.cc b/net/dns/mdns_cache.cc index b430f95..010a34f 100644 --- a/net/dns/mdns_cache.cc +++ b/net/dns/mdns_cache.cc @@ -4,6 +4,7 @@ #include "net/dns/mdns_cache.h" +#include <algorithm> #include <utility> #include "base/stl_util.h" @@ -89,31 +90,32 @@ const RecordParsed* MDnsCache::LookupKey(const Key& key) { MDnsCache::UpdateType MDnsCache::UpdateDnsRecord( scoped_ptr<const RecordParsed> record) { - UpdateType type = NoChange; - Key cache_key = Key::CreateFor(record.get()); - base::Time expiration = GetEffectiveExpiration(record.get()); - if (next_expiration_ == base::Time() || expiration < next_expiration_) { - next_expiration_ = expiration; - } + // Ignore "goodbye" packets for records not in cache. + if (record->ttl() == 0 && mdns_cache_.find(cache_key) == mdns_cache_.end()) + return NoChange; + + base::Time new_expiration = GetEffectiveExpiration(record.get()); + if (next_expiration_ != base::Time()) + new_expiration = std::min(new_expiration, next_expiration_); std::pair<RecordMap::iterator, bool> insert_result = mdns_cache_.insert(std::make_pair(cache_key, (const RecordParsed*)NULL)); - + UpdateType type = NoChange; if (insert_result.second) { type = RecordAdded; - insert_result.first->second = record.release(); } else { const RecordParsed* other_record = insert_result.first->second; - if (!record->IsEqual(other_record, true)) { + if (record->ttl() != 0 && !record->IsEqual(other_record, true)) { type = RecordChanged; } delete other_record; - insert_result.first->second = record.release(); } + insert_result.first->second = record.release(); + next_expiration_ = new_expiration; return type; } diff --git a/net/dns/mdns_cache_unittest.cc b/net/dns/mdns_cache_unittest.cc index 8ac30ac..4e313e5 100644 --- a/net/dns/mdns_cache_unittest.cc +++ b/net/dns/mdns_cache_unittest.cc @@ -107,6 +107,38 @@ static const uint8 kTestResponseTwoRecords[] = { '\x5f', '\x79', '\x5f', '\x79', }; +static const uint8 kTestResponsesGoodbyePacket[] = { + // Answer 1 + // ghs.l.google.com in DNS format. (Goodbye packet) + '\x03', 'g', 'h', 's', + '\x01', 'l', + '\x06', 'g', 'o', 'o', 'g', 'l', 'e', + '\x03', 'c', 'o', 'm', + '\x00', + '\x00', '\x01', // TYPE is A. + '\x00', '\x01', // CLASS is IN. + '\x00', '\x00', // TTL (4 bytes) is zero. + '\x00', '\x00', + '\x00', '\x04', // RDLENGTH is 4 bytes. + '\x4a', '\x7d', // RDATA is the IP: 74.125.95.121 + '\x5f', '\x79', + + // Answer 2 + // ghs.l.google.com in DNS format. + '\x03', 'g', 'h', 's', + '\x01', 'l', + '\x06', 'g', 'o', 'o', 'g', 'l', 'e', + '\x03', 'c', 'o', 'm', + '\x00', + '\x00', '\x01', // TYPE is A. + '\x00', '\x01', // CLASS is IN. + '\x00', '\x00', // TTL (4 bytes) is 53 seconds. + '\x00', '\x35', + '\x00', '\x04', // RDLENGTH is 4 bytes. + '\x4a', '\x7d', // RDATA is the IP: 74.125.95.121 + '\x5f', '\x79', +}; + class RecordRemovalMock { public: MOCK_METHOD1(OnRecordRemoved, void(const RecordParsed*)); @@ -266,6 +298,40 @@ TEST_F(MDnsCacheTest, RecordPreemptExpirationTime) { EXPECT_EQ(default_time_ + ttl1, cache_.next_expiration()); } +// Test that the cache handles mDNS "goodbye" packets correctly, not adding the +// records to the cache if they are not already there, and eventually removing +// records from the cache if they are. +TEST_F(MDnsCacheTest, GoodbyePacket) { + DnsRecordParser parser(kTestResponsesGoodbyePacket, + sizeof(kTestResponsesGoodbyePacket), + 0); + + scoped_ptr<const RecordParsed> record_goodbye; + scoped_ptr<const RecordParsed> record_hello; + scoped_ptr<const RecordParsed> record_goodbye2; + std::vector<const RecordParsed*> results; + + record_goodbye = RecordParsed::CreateFrom(&parser, default_time_); + record_hello = RecordParsed::CreateFrom(&parser, default_time_); + parser = DnsRecordParser(kTestResponsesGoodbyePacket, + sizeof(kTestResponsesGoodbyePacket), + 0); + record_goodbye2 = RecordParsed::CreateFrom(&parser, default_time_); + + base::TimeDelta ttl = base::TimeDelta::FromSeconds(record_hello->ttl()); + + EXPECT_EQ(base::Time(), cache_.next_expiration()); + EXPECT_EQ(MDnsCache::NoChange, cache_.UpdateDnsRecord(record_goodbye.Pass())); + EXPECT_EQ(base::Time(), cache_.next_expiration()); + EXPECT_EQ(MDnsCache::RecordAdded, + cache_.UpdateDnsRecord(record_hello.Pass())); + EXPECT_EQ(default_time_ + ttl, cache_.next_expiration()); + EXPECT_EQ(MDnsCache::NoChange, + cache_.UpdateDnsRecord(record_goodbye2.Pass())); + EXPECT_EQ(default_time_ + base::TimeDelta::FromSeconds(1), + cache_.next_expiration()); +} + TEST_F(MDnsCacheTest, AnyRRType) { DnsRecordParser parser(kTestResponseTwoRecords, sizeof(kTestResponseTwoRecords), diff --git a/net/dns/mdns_client_unittest.cc b/net/dns/mdns_client_unittest.cc index b19d63e..89f6bcf 100644 --- a/net/dns/mdns_client_unittest.cc +++ b/net/dns/mdns_client_unittest.cc @@ -310,6 +310,29 @@ const char kSamplePacketAPrivet[] = { '\x00', '\x02', }; +const char kSamplePacketGoodbye[] = { + // Header + '\x00', '\x00', // ID is zeroed out + '\x81', '\x80', // Standard query response, RA, no error + '\x00', '\x00', // No questions (for simplicity) + '\x00', '\x01', // 2 RRs (answers) + '\x00', '\x00', // 0 authority RRs + '\x00', '\x00', // 0 additional RRs + + // Answer 1 + '\x07', '_', 'p', 'r', 'i', 'v', 'e', 't', + '\x04', '_', 't', 'c', 'p', + '\x05', 'l', 'o', 'c', 'a', 'l', + '\x00', + '\x00', '\x0c', // TYPE is PTR. + '\x00', '\x01', // CLASS is IN. + '\x00', '\x00', // TTL (4 bytes) is zero; + '\x00', '\x00', + '\x00', '\x08', // RDLENGTH is 8 bytes. + '\x05', 'z', 'z', 'z', 'z', 'z', + '\xc0', '\x0c', +}; + class PtrRecordCopyContainer { public: PtrRecordCopyContainer() {} @@ -785,6 +808,38 @@ TEST_F(MDnsTest, TransactionReentrantCacheLookupStart) { SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1)); } +TEST_F(MDnsTest, GoodbyePacketNotification) { + StrictMock<MockListenerDelegate> delegate_privet; + + scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener( + dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet); + ASSERT_TRUE(listener_privet->Start()); + + SimulatePacketReceive(kSamplePacketGoodbye, sizeof(kSamplePacketGoodbye)); + + RunFor(base::TimeDelta::FromSeconds(2)); +} + +TEST_F(MDnsTest, GoodbyePacketRemoval) { + StrictMock<MockListenerDelegate> delegate_privet; + + scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener( + dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet); + ASSERT_TRUE(listener_privet->Start()); + + EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _)) + .Times(Exactly(1)); + + SimulatePacketReceive(kSamplePacket2, sizeof(kSamplePacket2)); + + SimulatePacketReceive(kSamplePacketGoodbye, sizeof(kSamplePacketGoodbye)); + + EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_REMOVED, _)) + .Times(Exactly(1)); + + RunFor(base::TimeDelta::FromSeconds(2)); +} + // In order to reliably test reentrant listener deletes, we create two listeners // and have each of them delete both, so we're guaranteed to try and deliver a // callback to at least one deleted listener. |