packetcache.cc \
qtype.cc \
rcpgenerator.cc \
+ recpacketcache.cc \
responsestats.cc \
responsestats-auth.cc \
sillyrecords.cc \
test-nmtree.cc \
test-packetcache_cc.cc \
test-rcpgenerator_cc.cc \
+ test-recpacketcache_cc.cc \
test-sha_hh.cc \
test-statbag_cc.cc \
test-zoneparser_tng_cc.cc \
uint32_t hashQuestion(const char* packet, uint16_t len, uint32_t init);
-//! compares two dns packets in canonical order, skipping the header, but including the question and the qtype
-inline bool dnspacketLessThan(const std::string& a, const std::string& b)
-{
- /* BEFORE YOU ATTEMPT TO MERGE THIS WITH DNSNAME::CANONICALCOMPARE!
- Please note that that code is subtly different, and for example only has
- to deal with a string of labels, and not a trailing packet. Also, the comparison
- rules are different since we also have to take into account qname and qtype.
- So just grin and bear it.
- */
-
- if(a.length() <= 12 || b.length() <= 12)
- return a.length() < b.length();
-
- uint8_t ourpos[64], rhspos[64];
- uint8_t ourcount=0, rhscount=0;
- //cout<<"Asked to compare "<<toString()<<" to "<<rhs.toString()<<endl;
- const unsigned char* p;
- for(p = (const unsigned char*)a.c_str()+12; p < (const unsigned char*)a.c_str() + a.size() && *p && ourcount < sizeof(ourpos); p+=*p+1)
- ourpos[ourcount++]=(p-(const unsigned char*)a.c_str());
- if(p>=(const unsigned char*)a.c_str() + a.size())
- return true;
-
- uint16_t aQtype = *(p+1)*256 + *(p+2);
- uint16_t aQclass =*(p+3)*256 + *(p+4);
-
- for(p = (const unsigned char*)b.c_str()+12; p < (const unsigned char*)b.c_str() + b.size() && *p && rhscount < sizeof(rhspos); p+=*p+1)
- rhspos[rhscount++]=(p-(const unsigned char*)b.c_str());
-
- if(p>=(const unsigned char*)b.c_str() + b.size())
- return false;
-
- uint16_t bQtype = *(p+1)*256 + *(p+2);
- uint16_t bQclass =*(p+3)*256 + *(p+4);
-
- if(ourcount == sizeof(ourpos) || rhscount==sizeof(rhspos)) {
- DNSName aname(a.c_str(), a.size(), 12, false, &aQtype, &aQclass);
- DNSName bname(b.c_str(), b.size(), 12, false, &bQtype, &bQclass);
-
- if(aname.slowCanonCompare(bname))
- return true;
- if(aname!=bname)
- return false;
- return boost::tie(aQtype, aQclass) < boost::tie(bQtype, bQclass);
- }
- for(;;) {
- if(ourcount == 0 && rhscount != 0)
- return true;
- if(ourcount == 0 && rhscount == 0)
- break;
- if(ourcount !=0 && rhscount == 0)
- return false;
- ourcount--;
- rhscount--;
-
- bool res=std::lexicographical_compare(
- a.c_str() + ourpos[ourcount] + 1,
- a.c_str() + ourpos[ourcount] + 1 + *(a.c_str() + ourpos[ourcount]),
- b.c_str() + rhspos[rhscount] + 1,
- b.c_str() + rhspos[rhscount] + 1 + *(b.c_str() + rhspos[rhscount]),
- [](const char& a, const char& b) {
- return dns2_tolower(a) < dns2_tolower(b);
- });
-
- // cout<<"Forward: "<<res<<endl;
- if(res)
- return true;
-
- res=std::lexicographical_compare( b.c_str() + rhspos[rhscount] + 1,
- b.c_str() + rhspos[rhscount] + 1 + *(b.c_str() + rhspos[rhscount]),
- a.c_str() + ourpos[ourcount] + 1,
- a.c_str() + ourpos[ourcount] + 1 + *(a.c_str() + ourpos[ourcount]),
- [](const char& a, const char& b) {
- return dns2_tolower(a) < dns2_tolower(b);
- });
- // cout<<"Reverse: "<<res<<endl;
- if(res)
- return false;
- }
-
- return boost::tie(aQtype, aQclass) < boost::tie(bQtype, bQclass);
-}
-
-
struct TSIGTriplet
{
DNSName name, algo;
ComboAddress d_remote, d_local;
bool d_tcp;
int d_socket;
+ int d_tag;
+ string d_query;
shared_ptr<TCPConnection> d_tcpConnection;
};
L<<Logger::Warning<<"Sending UDP reply to client "<<dc->d_remote.toStringWithPort()<<" failed with: "<<strerror(errno)<<endl;
if(!SyncRes::s_nopacketcache && !variableAnswer && !sr.wasVariable() ) {
- t_packetCache->insertResponsePacket(string((const char*)&*packet.begin(), packet.size()),
- (edo.d_Z & EDNSOpts::DNSSECOK), // ponder filtering on dnssecmode here
+ t_packetCache->insertResponsePacket(dc->d_tag, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_query, string((const char*)&*packet.begin(), packet.size()),
g_now.tv_sec,
min(minTTL,
(pw.getHeader()->rcode == RCode::ServFail) ? SyncRes::s_packetcacheservfailttl : SyncRes::s_packetcachettl
string response;
const struct dnsheader* dh = (struct dnsheader*)question.c_str();
+ unsigned int ctag=0;
try {
uint32_t age;
#ifdef MALLOC_TRACE
g_mtracer->clearAllocators();
*/
#endif
- bool needsDNSSEC=false;
-
- if(dh->arcount) {
+ if((*t_pdl)->d_gettag) {
unsigned int consumed=0;
- DNSName qname(question.c_str(), question.length(), sizeof(dnsheader), false, 0, 0, &consumed);
- if(question.size() > (consumed+12+11) && ((question[consumed+12+11]&0x80)==0x80))
- needsDNSSEC=true;
+ uint16_t qtype=0;
+ DNSName qname(question.c_str(), question.length(), sizeof(dnsheader), false, &qtype, 0, &consumed);
+ ctag=(*t_pdl)->gettag(fromaddr, destaddr, qname, qtype);
}
-
- if(!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(question, needsDNSSEC, g_now.tv_sec, &response, &age)) {
- if(!g_quiet)
- L<<Logger::Notice<<t_id<< " question answered from packet cache from "<<fromaddr.toString()<<endl;
- // t_queryring->push_back("packetcached");
-
+ if(!SyncRes::s_nopacketcache && t_packetCache->getResponsePacket(ctag, question, g_now.tv_sec, &response, &age)) {
+ if(!g_quiet)
+ L<<Logger::Notice<<t_id<< " question answered from packet cache tag="<<ctag<<" from "<<fromaddr.toString()<<endl;
g_stats.packetCacheHits++;
SyncRes::s_queries++;
DNSComboWriter* dc = new DNSComboWriter(question.c_str(), question.size(), g_now);
dc->setSocket(fd);
+ dc->d_tag=ctag;
+ dc->d_query = question;
dc->setRemote(&fromaddr);
dc->setLocal(destaddr);
dc->d_tcp=false;
int RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qtype, bool subtree)
{
- vector<uint8_t> packet;
- DNSPacketWriter pw(packet, name, 0);
- pw.getHeader()->rd=1;
- Entry e;
- e.d_packet.assign((const char*)&*packet.begin(), packet.size());
- e.d_wantsDNSSEC=false;
- // so the idea is, we search for a packet with qtype=0, which is ahead of anything with that name
-
int count=0;
- for(auto iter = d_packetCache.lower_bound(e); iter != d_packetCache.end(); ) {
- const struct dnsheader* packet = reinterpret_cast<const struct dnsheader*>((*iter).d_packet.c_str());
- if(packet->qdcount==0)
- break;
- uint16_t t;
-
- DNSName found(iter->d_packet.c_str(), iter->d_packet.size(), 12, false, &t);
- // cout<<"At record "<<found<<" while searching for "<<name<<", subtree= "<<subtree<<endl;
+ auto& idx = d_packetCache.get<NameTag>();
+ for(auto iter = idx.lower_bound(name); iter != idx.end(); ) {
if(subtree) {
- if(!found.isPartOf(name)) { // this is case insensitive
+ if(!iter->d_name.isPartOf(name)) { // this is case insensitive
break;
}
}
else {
- if(found != name)
+ if(iter->d_name != name)
break;
}
-
- if(t==qtype || qtype==0xffff) {
- iter=d_packetCache.erase(iter);
+
+ if(qtype==0xffff || iter->d_type == qtype) {
+ iter=idx.erase(iter);
count++;
}
else
++iter;
}
- // cout<<"Wiped "<<count<<" packets from cache"<<endl;
return count;
}
-bool RecursorPacketCache::getResponsePacket(const std::string& queryPacket, bool wantsDNSSEC, time_t now,
- std::string* responsePacket, uint32_t* age)
+// one day this function could be really fast by doing only a case insensitive compare
+static bool qrMatch(const std::string& query, const std::string& response)
{
- struct Entry e;
- e.d_packet=queryPacket;
- e.d_wantsDNSSEC = wantsDNSSEC;
+ uint16_t rqtype, rqclass, qqtype, qqclass;
+ DNSName queryname(query.c_str(), query.length(), sizeof(dnsheader), false, &qqtype, &qqclass, 0);
+ DNSName respname(response.c_str(), response.length(), sizeof(dnsheader), false, &rqtype, &rqclass, 0);
- packetCache_t::const_iterator iter = d_packetCache.find(e);
-
- if(iter == d_packetCache.end()) {
+ return queryname==respname && rqtype == qqtype && rqclass == qqclass;
+}
+
+uint32_t RecursorPacketCache::canHashPacket(const std::string& origPacket)
+{
+ // return 42; // should still work if you do this!
+ uint32_t ret=0;
+ ret=burtle((const unsigned char*)origPacket.c_str() + 2, 10, ret); // rest of dnsheader, skip id
+ const char* end = origPacket.c_str() + origPacket.size();
+ const char* p = origPacket.c_str() + 12;
+
+ for(; p < end && *p; ++p) { // XXX if you embed a 0 in your qname we'll stop lowercasing there
+ const char l = dns_tolower(*p); // label lengths can safely be lower cased
+ ret=burtle((const unsigned char*)&l, 1, ret);
+ }
+ return burtle((const unsigned char*)p, end-p, ret);
+}
+
+bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now,
+ std::string* responsePacket, uint32_t* age)
+{
+ uint32_t h = canHashPacket(queryPacket);
+ auto& idx = d_packetCache.get<HashTag>();
+ auto range = idx.equal_range(tie(tag,h));
+
+ if(range.first == range.second) {
d_misses++;
return false;
}
- if((uint32_t)now < iter->d_ttd) { // it is fresh!
-// cerr<<"Fresh for another "<<iter->d_ttd - now<<" seconds!"<<endl;
- *age = now - iter->d_creation;
- uint16_t id;
- memcpy(&id, queryPacket.c_str(), 2);
- *responsePacket = iter->d_packet;
- responsePacket->replace(0, 2, (char*)&id, 2);
+ for(auto iter = range.first ; iter != range.second ; ++ iter) {
+ // the possibility is VERY real that we get hits that are not right - birthday paradox
+ if(!qrMatch(queryPacket, iter->d_packet))
+ continue;
+ if((uint32_t)now < iter->d_ttd) { // it is right, it is fresh!
+ *age = now - iter->d_creation;
+ *responsePacket = iter->d_packet;
+ responsePacket->replace(0, 2, queryPacket.c_str(), 2);
- string::size_type i=sizeof(dnsheader);
-
- for(;;) {
- int labellen = (unsigned char)queryPacket[i];
- if(!labellen || i + labellen > responsePacket->size()) break;
- i++;
- responsePacket->replace(i, labellen, queryPacket, i, labellen);
- i = i + labellen;
- }
-
- d_hits++;
- moveCacheItemToBack(d_packetCache, iter);
+ string::size_type i=sizeof(dnsheader);
+
+ for(;;) {
+ int labellen = (unsigned char)queryPacket[i];
+ if(!labellen || i + labellen > responsePacket->size()) break;
+ i++;
+ responsePacket->replace(i, labellen, queryPacket, i, labellen);
+ i = i + labellen;
+ }
- return true;
+ d_hits++;
+ moveCacheItemToBack(d_packetCache, iter);
+
+ return true;
+ }
+ else {
+ moveCacheItemToFront(d_packetCache, iter);
+ d_misses++;
+ break;
+ }
}
- moveCacheItemToFront(d_packetCache, iter);
- d_misses++;
+
return false;
}
-void RecursorPacketCache::insertResponsePacket(const std::string& responsePacket, bool wantsDNSSEC, time_t now, uint32_t ttl)
+
+void RecursorPacketCache::insertResponsePacket(unsigned int tag, const DNSName& qname, uint16_t qtype, const std::string& queryPacket, const std::string& responsePacket, time_t now, uint32_t ttl)
{
- struct Entry e;
- e.d_packet = responsePacket;
- e.d_wantsDNSSEC = wantsDNSSEC;
- e.d_ttd = now+ttl;
- e.d_creation = now;
- packetCache_t::iterator iter = d_packetCache.find(e);
-
- if(iter != d_packetCache.end()) {
+ auto qhash = canHashPacket(queryPacket);
+ auto& idx = d_packetCache.get<HashTag>();
+ auto range = idx.equal_range(tie(tag,qhash));
+ auto iter = range.first;
+
+ for( ; iter != range.second ; ++iter) {
+ if(iter->d_type != qtype)
+ continue;
+ // this only happens on insert which is relatively rare and does not need to be super fast
+ DNSName respname(iter->d_packet.c_str(), iter->d_packet.length(), sizeof(dnsheader), false, 0, 0, 0);
+ if(qname != respname)
+ continue;
iter->d_packet = responsePacket;
iter->d_ttd = now + ttl;
iter->d_creation = now;
+ break;
}
- else
+
+ if(iter == range.second) { // nothing to refresh
+ struct Entry e;
+ e.d_packet = responsePacket;
+ e.d_name = qname;
+ e.d_qhash = qhash;
+ e.d_type = qtype;
+ e.d_ttd = now+ttl;
+ e.d_creation = now;
+ e.d_tag = tag;
d_packetCache.insert(e);
+ }
}
uint64_t RecursorPacketCache::size()
return sum;
}
-
void RecursorPacketCache::doPruneTo(unsigned int maxCached)
{
pruneCollection(d_packetCache, maxCached);
#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/multi_index/sequenced_index.hpp>
{
public:
RecursorPacketCache();
- bool getResponsePacket(const std::string& queryPacket, bool wantsDNSSEC, time_t now, std::string* responsePacket, uint32_t* age);
- void insertResponsePacket(const std::string& responsePacket, bool wantsDNSSEC, time_t now, uint32_t ttd);
+ bool getResponsePacket(unsigned int tag, const std::string& queryPacket, time_t now, std::string* responsePacket, uint32_t* age);
+ void insertResponsePacket(unsigned int tag, const DNSName& qname, uint16_t qtype, const std::string& queryPacket, const std::string& responsePacket, time_t now, uint32_t ttd);
void doPruneTo(unsigned int maxSize=250000);
int doWipePacketCache(const DNSName& name, uint16_t qtype=0xffff, bool subtree=false);
uint64_t bytes();
private:
-
+ struct HashTag {};
+ struct NameTag {};
struct Entry
{
mutable uint32_t d_ttd;
- mutable uint32_t d_creation;
+ mutable uint32_t d_creation; // so we can 'age' our packets
+ DNSName d_name;
+ uint16_t d_type;
mutable std::string d_packet; // "I know what I am doing"
- bool d_wantsDNSSEC;
+ uint32_t d_qhash;
+ uint32_t d_tag;
inline bool operator<(const struct Entry& rhs) const;
uint32_t getTTD() const
return d_ttd;
}
};
-
+ uint32_t canHashPacket(const std::string& origPacket);
typedef multi_index_container<
Entry,
indexed_by <
- ordered_unique<identity<Entry> >,
- sequenced<>
- >
+ hashed_non_unique<tag<HashTag>, composite_key<Entry, member<Entry,uint32_t,&Entry::d_tag>, member<Entry,uint32_t,&Entry::d_qhash> > >,
+ sequenced<> ,
+ ordered_non_unique<tag<NameTag>, member<Entry,DNSName,&Entry::d_name>, CanonDNSNameCompare >
+ >
> packetCache_t;
- packetCache_t d_packetCache;
+ packetCache_t d_packetCache;
};
-// needs to take into account: qname, qtype, opcode, rd, qdcount, EDNS size
-inline bool RecursorPacketCache::Entry::operator<(const struct RecursorPacketCache::Entry &rhs) const
-{
- const struct dnsheader*
- dh=(const struct dnsheader*) d_packet.c_str(),
- *rhsdh=(const struct dnsheader*)rhs.d_packet.c_str();
- if(std::tie(d_wantsDNSSEC, dh->opcode, dh->rd, dh->qdcount) <
- std::tie(rhs.d_wantsDNSSEC, rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
- return true;
-
- if(std::tie(d_wantsDNSSEC, dh->opcode, dh->rd, dh->qdcount) >
- std::tie(rhs.d_wantsDNSSEC, rhsdh->opcode, rhsdh->rd, rhsdh->qdcount))
- return false;
-
- return dnspacketLessThan(d_packet, rhs.d_packet);
-}
-
-
-
#endif
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#include "dnswriter.hh"
+#include "dnsrecords.hh"
+#include "dns_random.hh"
+#include "iputils.hh"
+#include "recpacketcache.hh"
+#include <utility>
+
+
+BOOST_AUTO_TEST_SUITE(recpacketcache_cc)
+
+BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
+ RecursorPacketCache rpc;
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+ DNSName qname("www.powerdns.com");
+ vector<uint8_t> packet;
+ DNSPacketWriter pw(packet, qname, QType::A);
+ pw.getHeader()->rd=true;
+ pw.getHeader()->qr=false;
+ pw.getHeader()->id=random();
+ string qpacket((const char*)&packet[0], packet.size());
+ pw.startRecord(qname, QType::A, 3600);
+
+ ARecordContent ar("127.0.0.1");
+ ar.toPacket(pw);
+ pw.commit();
+ string rpacket((const char*)&packet[0], packet.size());
+
+ rpc.insertResponsePacket(0,qname, QType::A, qpacket, rpacket, time(0), 3600);
+ BOOST_CHECK_EQUAL(rpc.size(), 1);
+ rpc.doPruneTo(0);
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+ rpc.insertResponsePacket(0,qname, QType::A, qpacket, rpacket, time(0), 3600);
+ BOOST_CHECK_EQUAL(rpc.size(), 1);
+ rpc.doWipePacketCache(qname);
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+ rpc.insertResponsePacket(0,qname, QType::A, qpacket, rpacket, time(0), 3600);
+ uint32_t age=0;
+ string fpacket;
+ bool found = rpc.getResponsePacket(0, qpacket, time(0), &fpacket, &age);
+ BOOST_CHECK_EQUAL(found, 1);
+ BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+ packet.clear();
+ qname+=DNSName("co.uk");
+ DNSPacketWriter pw2(packet, qname, QType::A);
+
+ pw2.getHeader()->rd=true;
+ pw2.getHeader()->qr=false;
+ pw2.getHeader()->id=random();
+ qpacket.assign((const char*)&packet[0], packet.size());
+ found = rpc.getResponsePacket(0, qpacket, time(0), &fpacket, &age);
+ BOOST_CHECK_EQUAL(found, 0);
+
+ rpc.doWipePacketCache(DNSName("com"), 0xffff, true);
+ BOOST_CHECK_EQUAL(rpc.size(), 0);
+
+
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()