root/trunk/pdns/pdns/packetcache.cc @ 1220

Revision 1220, 8.6 KB (checked in by ahu, 5 years ago)

implement fast wiping of "ds9a.nl" and "blah.ds9a.nl" using inverted ordering

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1/*
2    PowerDNS Versatile Database Driven Nameserver
3    Copyright (C) 2002 - 2008  PowerDNS.COM BV
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License version 2 as
7    published by the Free Software Foundation
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17*/
18#include "utility.hh"
19#include "packetcache.hh"
20#include "logger.hh"
21#include "arguments.hh"
22#include "statbag.hh"
23#include <map>
24#include <boost/algorithm/string.hpp>
25
26extern StatBag S;
27
28PacketCache::PacketCache()
29{
30  pthread_rwlock_init(&d_mut,0);
31  d_hit=d_miss=0;
32
33  d_ttl=-1;
34  d_recursivettl=-1;
35
36  S.declare("packetcache-hit");
37  S.declare("packetcache-miss");
38  S.declare("packetcache-size");
39
40  statnumhit=S.getPointer("packetcache-hit");
41  statnummiss=S.getPointer("packetcache-miss");
42  statnumentries=S.getPointer("packetcache-size");
43}
44
45int PacketCache::get(DNSPacket *p, DNSPacket *cached)
46{
47  extern StatBag S;
48  if(!((d_hit+d_miss)%5000)) {
49    cleanup();
50  }
51
52  if(d_ttl<0) 
53    getTTLS();
54
55  if(d_doRecursion && p->d.rd) { // wants recursion
56    if(!d_recursivettl) {
57      (*statnummiss)++;
58      d_miss++;
59      return 0;
60    }
61  }
62  else { // does not
63    if(!d_ttl) {
64      (*statnummiss)++;
65      d_miss++;
66      return 0;
67    }
68  }
69   
70  bool packetMeritsRecursion=d_doRecursion && p->d.rd;
71
72  if(ntohs(p->d.qdcount)!=1) // we get confused by packets with more than one question
73    return 0;
74
75  {
76    TryReadLock l(&d_mut); // take a readlock here
77    if(!l.gotIt()) {
78      S.inc("deferred-cache-lookup");
79      return 0;
80    }
81
82    if(!((d_hit+d_miss)%1000)) {
83      *statnumentries=d_map.size(); // needs lock
84    }
85    string value;
86
87    if(getEntry(p->qdomain, p->qtype, PacketCache::PACKETCACHE, value, -1, packetMeritsRecursion)) {
88      //      cerr<<"Packet cache hit!"<<endl;
89      (*statnumhit)++;
90      d_hit++;
91      if(cached->parse(value.c_str(), value.size()) < 0) {
92        return -1;
93      }
94      cached->spoofQuestion(p->qdomain); // for correct case
95      return 1;
96    }
97  }
98  //   cerr<<"Packet cache miss"<<endl;
99  (*statnummiss)++;
100  d_miss++;
101  return 0; // bummer
102}
103
104void PacketCache::getTTLS()
105{
106  d_ttl=::arg().asNum("cache-ttl");
107  d_recursivettl=::arg().asNum("recursive-cache-ttl");
108
109  d_doRecursion=::arg().mustDo("recursor"); 
110}
111
112
113void PacketCache::insert(DNSPacket *q, DNSPacket *r)
114{
115  if(d_ttl < 0)
116    getTTLS();
117 
118  if(ntohs(q->d.qdcount)!=1) {
119    return; // do not try to cache packets with multiple questions
120  }
121
122  bool packetMeritsRecursion=d_doRecursion && q->d.rd;
123
124  insert(q->qdomain, q->qtype, PacketCache::PACKETCACHE, r->getString(), packetMeritsRecursion ? d_recursivettl : d_ttl);  // XXX FIXME forgets meritsRecursion
125}
126
127// universal key appears to be: qname, qtype, kind (packet, query cache), optionally zoneid, meritsRecursion
128void PacketCache::insert(const string &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID, bool meritsRecursion)
129{
130  if(!ttl)
131    return;
132 
133  //  cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", value: '"<< (cet ? value : "PACKET") <<"', qtype: "<<qtype.getName()<<endl;
134
135  CacheEntry val;
136  val.ttd=time(0)+ttl;
137  val.qname=qname;
138  val.qtype=qtype.getCode();
139  val.value=value;
140  val.ctype=cet;
141  val.meritsRecursion=meritsRecursion;
142
143  TryWriteLock l(&d_mut);
144  if(l.gotIt()) { 
145    bool success;
146    cmap_t::iterator place;
147    tie(place, success)=d_map.insert(val);
148    //    cerr<<"Insert succeeded: "<<success<<endl;
149    if(!success)
150      d_map.replace(place, val);
151   
152  }
153  else 
154    S.inc("deferred-cache-inserts"); 
155}
156
157/** purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
158int PacketCache::purge(const string &match)
159{
160  WriteLock l(&d_mut);
161  int delcount=0;
162
163  if(match.empty()) {
164    delcount = d_map.size();
165    d_map.clear();
166    *statnumentries=0;
167    return delcount;
168  }
169
170  /* ok, the suffix delete plan. We want to be able to delete everything that
171     pertains 'www.powerdns.com' but we also want to be able to delete everything
172     in the powerdns.com zone, so: 'powerdns.com' and '*.powerdns.com'.
173
174     However, we do NOT want to delete 'usepowerdns.com!, nor 'powerdnsiscool.com'
175
176     So, at first shot, store in reverse label order:
177
178     'be.someotherdomain'
179     'com.powerdns'
180     'com.powerdns.images'
181     'com.powerdns.www'
182     'com.powerdnsiscool'
183     'com.usepowerdns.www'
184
185     If we get a request to remove 'everything above powerdns.com', we do a search for 'com.powerdns' which is guaranteed to come first (it is shortest!)
186     Then we delete everything that is either equal to 'com.powerdns' or begins with 'com.powerdns.' This trailing dot saves us
187     from deleting 'com.powerdnsiscool'.
188
189     We can stop the process once we reach something that doesn't match.
190
191     Ok - fine so far, except it doesn't work! Let's say there *is* no 'com.powerdns' in cache!
192
193     In that case our request doesn't find anything.. now what.
194     lower_bound to the rescue! It finds the place where 'com.powerdns' *would* be.
195     
196     Ok - next step, can we get away with simply reversing the string?
197
198     'moc.sndrewop'
199     'moc.sndrewop.segami'
200     'moc.sndrewop.www'
201     'moc.loocsidnsrewop'
202     'moc.dnsrewopesu.www'
203
204     Ok - next step, can we get away with only reversing the comparison?
205
206     'powerdns.com'
207     'images.powerdns.com'
208     '   www.powerdns.com'
209     'powerdnsiscool.com'
210     'www.userpowerdns.com'
211
212  */
213  if(ends_with(match, "$")) {
214    string suffix(match);
215    suffix.resize(suffix.size()-1);
216
217    //    cerr<<"Begin dump!"<<endl;
218    cmap_t::const_iterator iter = d_map.lower_bound(tie(suffix));
219    cmap_t::const_iterator start=iter;
220    string dotsuffix = "."+suffix;
221
222    for(; iter != d_map.end(); ++iter) {
223      if(!iequals(iter->qname, suffix) && !iends_with(iter->qname, dotsuffix)) {
224        //      cerr<<"Stopping!"<<endl;
225        break;
226      }
227      //      cerr<<"Will erase '"<<iter->qname<<"'\n";
228
229      delcount++;
230    }
231    //    cerr<<"End dump!"<<endl;
232    d_map.erase(start, iter);
233  }
234  else {
235    delcount=d_map.count(tie(match));
236    pair<cmap_t::iterator, cmap_t::iterator> range = d_map.equal_range(tie(match));
237    d_map.erase(range.first, range.second);
238  }
239  *statnumentries=d_map.size();
240  return delcount;
241}
242
243bool PacketCache::getEntry(const string &qname, const QType& qtype, CacheEntryType cet, string& value, int zoneID, bool meritsRecursion)
244{
245  TryReadLock l(&d_mut); // take a readlock here
246  if(!l.gotIt()) {
247    S.inc( "deferred-cache-lookup");
248    return false;
249  }
250
251  uint16_t qt = qtype.getCode();
252  cmap_t::const_iterator i=d_map.find(tie(qname, qt, cet, zoneID, meritsRecursion));
253  time_t now=time(0);
254  bool ret=(i!=d_map.end() && i->ttd > now);
255  if(ret)
256    value = i->value;
257 
258  //  cerr<<"Cache hit: "<<(int)cet<<", "<<ret<<endl;
259 
260  return ret;
261}
262
263// FIXME: must be converted to boost multi index
264map<char,int> PacketCache::getCounts()
265{
266  ReadLock l(&d_mut);
267
268  map<char,int>ret;
269  return ret;
270}
271
272int PacketCache::size()
273{
274  ReadLock l(&d_mut);
275  return d_map.size();
276}
277
278/** readlock for figuring out which iterators to delete, upgrade to writelock when actually cleaning */
279void PacketCache::cleanup()
280{
281  WriteLock l(&d_mut);
282
283  *statnumentries=d_map.size();
284
285  unsigned int maxCached=::arg().asNum("max-cache-entries");
286  unsigned int toTrim=0;
287 
288  unsigned int cacheSize=*statnumentries;
289
290  if(maxCached && cacheSize > maxCached) {
291    toTrim = cacheSize - maxCached;
292  }
293
294  unsigned int lookAt=0;
295  // two modes - if toTrim is 0, just look through 10000 records and nuke everything that is expired
296  // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
297  if(toTrim)
298    lookAt=5*toTrim;
299  else
300    lookAt=cacheSize/10;
301
302  //  cerr<<"cacheSize: "<<cacheSize<<", lookAt: "<<lookAt<<", toTrim: "<<toTrim<<endl;
303  time_t now=time(0);
304
305  DLOG(L<<"Starting cache clean"<<endl);
306  if(d_map.empty())
307    return; // clean
308
309  typedef cmap_t::nth_index<1>::type sequence_t;
310  sequence_t& sidx=d_map.get<1>();
311  unsigned int erased=0;
312  for(sequence_t::iterator i=sidx.begin(); i != sidx.end();) {
313    if(i->ttd < now) {
314      sidx.erase(i++);
315      erased++;
316    }
317    else
318      ++i;
319
320    if(toTrim && erased > toTrim)
321      break;
322
323  }
324  //  cerr<<"erased: "<<erased<<endl;
325  *statnumentries=d_map.size();
326  DLOG(L<<"Done with cache clean"<<endl);
327}
Note: See TracBrowser for help on using the browser.