blob: 5497f453fecdf6b9b7247b41d0a4e686eee1d9f4 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.io.*;
6import java.util.*;
7
8/**
9 * A cache of DNS records. The cache obeys TTLs, so items are purged after
10 * their validity period is complete. Negative answers are cached, to
11 * avoid repeated failed DNS queries. The credibility of each RRset is
12 * maintained, so that more credible records replace less credible records,
13 * and lookups can specify the minimum credibility of data they are requesting.
14 * @see RRset
15 * @see Credibility
16 *
17 * @author Brian Wellington
18 */
19
20public class Cache {
21
22private interface Element {
23 public boolean expired();
24 public int compareCredibility(int cred);
25 public int getType();
26}
27
28private static int
29limitExpire(long ttl, long maxttl) {
30 if (maxttl >= 0 && maxttl < ttl)
31 ttl = maxttl;
32 long expire = (System.currentTimeMillis() / 1000) + ttl;
33 if (expire < 0 || expire > Integer.MAX_VALUE)
34 return Integer.MAX_VALUE;
35 return (int)expire;
36}
37
38private static class CacheRRset extends RRset implements Element {
39 private static final long serialVersionUID = 5971755205903597024L;
40
41 int credibility;
42 int expire;
43
44 public
45 CacheRRset(Record rec, int cred, long maxttl) {
46 super();
47 this.credibility = cred;
48 this.expire = limitExpire(rec.getTTL(), maxttl);
49 addRR(rec);
50 }
51
52 public
53 CacheRRset(RRset rrset, int cred, long maxttl) {
54 super(rrset);
55 this.credibility = cred;
56 this.expire = limitExpire(rrset.getTTL(), maxttl);
57 }
58
59 public final boolean
60 expired() {
61 int now = (int)(System.currentTimeMillis() / 1000);
62 return (now >= expire);
63 }
64
65 public final int
66 compareCredibility(int cred) {
67 return credibility - cred;
68 }
69
70 public String
71 toString() {
72 StringBuffer sb = new StringBuffer();
73 sb.append(super.toString());
74 sb.append(" cl = ");
75 sb.append(credibility);
76 return sb.toString();
77 }
78}
79
80private static class NegativeElement implements Element {
81 int type;
82 Name name;
83 int credibility;
84 int expire;
85
86 public
87 NegativeElement(Name name, int type, SOARecord soa, int cred,
88 long maxttl)
89 {
90 this.name = name;
91 this.type = type;
92 long cttl = 0;
93 if (soa != null)
94 cttl = soa.getMinimum();
95 this.credibility = cred;
96 this.expire = limitExpire(cttl, maxttl);
97 }
98
99 public int
100 getType() {
101 return type;
102 }
103
104 public final boolean
105 expired() {
106 int now = (int)(System.currentTimeMillis() / 1000);
107 return (now >= expire);
108 }
109
110 public final int
111 compareCredibility(int cred) {
112 return credibility - cred;
113 }
114
115 public String
116 toString() {
117 StringBuffer sb = new StringBuffer();
118 if (type == 0)
119 sb.append("NXDOMAIN " + name);
120 else
121 sb.append("NXRRSET " + name + " " + Type.string(type));
122 sb.append(" cl = ");
123 sb.append(credibility);
124 return sb.toString();
125 }
126}
127
128private static class CacheMap extends LinkedHashMap {
129 private int maxsize = -1;
130
131 CacheMap(int maxsize) {
132 super(16, (float) 0.75, true);
133 this.maxsize = maxsize;
134 }
135
136 int
137 getMaxSize() {
138 return maxsize;
139 }
140
141 void
142 setMaxSize(int maxsize) {
143 /*
144 * Note that this doesn't shrink the size of the map if
145 * the maximum size is lowered, but it should shrink as
146 * entries expire.
147 */
148 this.maxsize = maxsize;
149 }
150
151 protected boolean removeEldestEntry(Map.Entry eldest) {
152 return maxsize >= 0 && size() > maxsize;
153 }
154}
155
156private CacheMap data;
157private int maxncache = -1;
158private int maxcache = -1;
159private int dclass;
160
161private static final int defaultMaxEntries = 50000;
162
163/**
164 * Creates an empty Cache
165 *
166 * @param dclass The DNS class of this cache
167 * @see DClass
168 */
169public
170Cache(int dclass) {
171 this.dclass = dclass;
172 data = new CacheMap(defaultMaxEntries);
173}
174
175/**
176 * Creates an empty Cache for class IN.
177 * @see DClass
178 */
179public
180Cache() {
181 this(DClass.IN);
182}
183
184/**
185 * Creates a Cache which initially contains all records in the specified file.
186 */
187public
188Cache(String file) throws IOException {
189 data = new CacheMap(defaultMaxEntries);
190 Master m = new Master(file);
191 Record record;
192 while ((record = m.nextRecord()) != null)
193 addRecord(record, Credibility.HINT, m);
194}
195
196private synchronized Object
197exactName(Name name) {
198 return data.get(name);
199}
200
201private synchronized void
202removeName(Name name) {
203 data.remove(name);
204}
205
206private synchronized Element []
207allElements(Object types) {
208 if (types instanceof List) {
209 List typelist = (List) types;
210 int size = typelist.size();
211 return (Element []) typelist.toArray(new Element[size]);
212 } else {
213 Element set = (Element) types;
214 return new Element[] {set};
215 }
216}
217
218private synchronized Element
219oneElement(Name name, Object types, int type, int minCred) {
220 Element found = null;
221
222 if (type == Type.ANY)
223 throw new IllegalArgumentException("oneElement(ANY)");
224 if (types instanceof List) {
225 List list = (List) types;
226 for (int i = 0; i < list.size(); i++) {
227 Element set = (Element) list.get(i);
228 if (set.getType() == type) {
229 found = set;
230 break;
231 }
232 }
233 } else {
234 Element set = (Element) types;
235 if (set.getType() == type)
236 found = set;
237 }
238 if (found == null)
239 return null;
240 if (found.expired()) {
241 removeElement(name, type);
242 return null;
243 }
244 if (found.compareCredibility(minCred) < 0)
245 return null;
246 return found;
247}
248
249private synchronized Element
250findElement(Name name, int type, int minCred) {
251 Object types = exactName(name);
252 if (types == null)
253 return null;
254 return oneElement(name, types, type, minCred);
255}
256
257private synchronized void
258addElement(Name name, Element element) {
259 Object types = data.get(name);
260 if (types == null) {
261 data.put(name, element);
262 return;
263 }
264 int type = element.getType();
265 if (types instanceof List) {
266 List list = (List) types;
267 for (int i = 0; i < list.size(); i++) {
268 Element elt = (Element) list.get(i);
269 if (elt.getType() == type) {
270 list.set(i, element);
271 return;
272 }
273 }
274 list.add(element);
275 } else {
276 Element elt = (Element) types;
277 if (elt.getType() == type)
278 data.put(name, element);
279 else {
280 LinkedList list = new LinkedList();
281 list.add(elt);
282 list.add(element);
283 data.put(name, list);
284 }
285 }
286}
287
288private synchronized void
289removeElement(Name name, int type) {
290 Object types = data.get(name);
291 if (types == null) {
292 return;
293 }
294 if (types instanceof List) {
295 List list = (List) types;
296 for (int i = 0; i < list.size(); i++) {
297 Element elt = (Element) list.get(i);
298 if (elt.getType() == type) {
299 list.remove(i);
300 if (list.size() == 0)
301 data.remove(name);
302 return;
303 }
304 }
305 } else {
306 Element elt = (Element) types;
307 if (elt.getType() != type)
308 return;
309 data.remove(name);
310 }
311}
312
313/** Empties the Cache. */
314public synchronized void
315clearCache() {
316 data.clear();
317}
318
319/**
320 * Adds a record to the Cache.
321 * @param r The record to be added
322 * @param cred The credibility of the record
323 * @param o The source of the record (this could be a Message, for example)
324 * @see Record
325 */
326public synchronized void
327addRecord(Record r, int cred, Object o) {
328 Name name = r.getName();
329 int type = r.getRRsetType();
330 if (!Type.isRR(type))
331 return;
332 Element element = findElement(name, type, cred);
333 if (element == null) {
334 CacheRRset crrset = new CacheRRset(r, cred, maxcache);
335 addRRset(crrset, cred);
336 } else if (element.compareCredibility(cred) == 0) {
337 if (element instanceof CacheRRset) {
338 CacheRRset crrset = (CacheRRset) element;
339 crrset.addRR(r);
340 }
341 }
342}
343
344/**
345 * Adds an RRset to the Cache.
346 * @param rrset The RRset to be added
347 * @param cred The credibility of these records
348 * @see RRset
349 */
350public synchronized void
351addRRset(RRset rrset, int cred) {
352 long ttl = rrset.getTTL();
353 Name name = rrset.getName();
354 int type = rrset.getType();
355 Element element = findElement(name, type, 0);
356 if (ttl == 0) {
357 if (element != null && element.compareCredibility(cred) <= 0)
358 removeElement(name, type);
359 } else {
360 if (element != null && element.compareCredibility(cred) <= 0)
361 element = null;
362 if (element == null) {
363 CacheRRset crrset;
364 if (rrset instanceof CacheRRset)
365 crrset = (CacheRRset) rrset;
366 else
367 crrset = new CacheRRset(rrset, cred, maxcache);
368 addElement(name, crrset);
369 }
370 }
371}
372
373/**
374 * Adds a negative entry to the Cache.
375 * @param name The name of the negative entry
376 * @param type The type of the negative entry
377 * @param soa The SOA record to add to the negative cache entry, or null.
378 * The negative cache ttl is derived from the SOA.
379 * @param cred The credibility of the negative entry
380 */
381public synchronized void
382addNegative(Name name, int type, SOARecord soa, int cred) {
383 long ttl = 0;
384 if (soa != null)
385 ttl = soa.getTTL();
386 Element element = findElement(name, type, 0);
387 if (ttl == 0) {
388 if (element != null && element.compareCredibility(cred) <= 0)
389 removeElement(name, type);
390 } else {
391 if (element != null && element.compareCredibility(cred) <= 0)
392 element = null;
393 if (element == null)
394 addElement(name, new NegativeElement(name, type,
395 soa, cred,
396 maxncache));
397 }
398}
399
400/**
401 * Finds all matching sets or something that causes the lookup to stop.
402 */
403protected synchronized SetResponse
404lookup(Name name, int type, int minCred) {
405 int labels;
406 int tlabels;
407 Element element;
408 Name tname;
409 Object types;
410 SetResponse sr;
411
412 labels = name.labels();
413
414 for (tlabels = labels; tlabels >= 1; tlabels--) {
415 boolean isRoot = (tlabels == 1);
416 boolean isExact = (tlabels == labels);
417
418 if (isRoot)
419 tname = Name.root;
420 else if (isExact)
421 tname = name;
422 else
423 tname = new Name(name, labels - tlabels);
424
425 types = data.get(tname);
426 if (types == null)
427 continue;
428
429 /*
430 * If this is the name, look for the actual type or a CNAME
431 * (unless it's an ANY query, where we return everything).
432 * Otherwise, look for a DNAME.
433 */
434 if (isExact && type == Type.ANY) {
435 sr = new SetResponse(SetResponse.SUCCESSFUL);
436 Element [] elements = allElements(types);
437 int added = 0;
438 for (int i = 0; i < elements.length; i++) {
439 element = elements[i];
440 if (element.expired()) {
441 removeElement(tname, element.getType());
442 continue;
443 }
444 if (!(element instanceof CacheRRset))
445 continue;
446 if (element.compareCredibility(minCred) < 0)
447 continue;
448 sr.addRRset((CacheRRset)element);
449 added++;
450 }
451 /* There were positive entries */
452 if (added > 0)
453 return sr;
454 } else if (isExact) {
455 element = oneElement(tname, types, type, minCred);
456 if (element != null &&
457 element instanceof CacheRRset)
458 {
459 sr = new SetResponse(SetResponse.SUCCESSFUL);
460 sr.addRRset((CacheRRset) element);
461 return sr;
462 } else if (element != null) {
463 sr = new SetResponse(SetResponse.NXRRSET);
464 return sr;
465 }
466
467 element = oneElement(tname, types, Type.CNAME, minCred);
468 if (element != null &&
469 element instanceof CacheRRset)
470 {
471 return new SetResponse(SetResponse.CNAME,
472 (CacheRRset) element);
473 }
474 } else {
475 element = oneElement(tname, types, Type.DNAME, minCred);
476 if (element != null &&
477 element instanceof CacheRRset)
478 {
479 return new SetResponse(SetResponse.DNAME,
480 (CacheRRset) element);
481 }
482 }
483
484 /* Look for an NS */
485 element = oneElement(tname, types, Type.NS, minCred);
486 if (element != null && element instanceof CacheRRset)
487 return new SetResponse(SetResponse.DELEGATION,
488 (CacheRRset) element);
489
490 /* Check for the special NXDOMAIN element. */
491 if (isExact) {
492 element = oneElement(tname, types, 0, minCred);
493 if (element != null)
494 return SetResponse.ofType(SetResponse.NXDOMAIN);
495 }
496
497 }
498 return SetResponse.ofType(SetResponse.UNKNOWN);
499}
500
501/**
502 * Looks up Records in the Cache. This follows CNAMEs and handles negatively
503 * cached data.
504 * @param name The name to look up
505 * @param type The type to look up
506 * @param minCred The minimum acceptable credibility
507 * @return A SetResponse object
508 * @see SetResponse
509 * @see Credibility
510 */
511public SetResponse
512lookupRecords(Name name, int type, int minCred) {
513 return lookup(name, type, minCred);
514}
515
516private RRset []
517findRecords(Name name, int type, int minCred) {
518 SetResponse cr = lookupRecords(name, type, minCred);
519 if (cr.isSuccessful())
520 return cr.answers();
521 else
522 return null;
523}
524
525/**
526 * Looks up credible Records in the Cache (a wrapper around lookupRecords).
527 * Unlike lookupRecords, this given no indication of why failure occurred.
528 * @param name The name to look up
529 * @param type The type to look up
530 * @return An array of RRsets, or null
531 * @see Credibility
532 */
533public RRset []
534findRecords(Name name, int type) {
535 return findRecords(name, type, Credibility.NORMAL);
536}
537
538/**
539 * Looks up Records in the Cache (a wrapper around lookupRecords). Unlike
540 * lookupRecords, this given no indication of why failure occurred.
541 * @param name The name to look up
542 * @param type The type to look up
543 * @return An array of RRsets, or null
544 * @see Credibility
545 */
546public RRset []
547findAnyRecords(Name name, int type) {
548 return findRecords(name, type, Credibility.GLUE);
549}
550
551private final int
552getCred(int section, boolean isAuth) {
553 if (section == Section.ANSWER) {
554 if (isAuth)
555 return Credibility.AUTH_ANSWER;
556 else
557 return Credibility.NONAUTH_ANSWER;
558 } else if (section == Section.AUTHORITY) {
559 if (isAuth)
560 return Credibility.AUTH_AUTHORITY;
561 else
562 return Credibility.NONAUTH_AUTHORITY;
563 } else if (section == Section.ADDITIONAL) {
564 return Credibility.ADDITIONAL;
565 } else
566 throw new IllegalArgumentException("getCred: invalid section");
567}
568
569private static void
570markAdditional(RRset rrset, Set names) {
571 Record first = rrset.first();
572 if (first.getAdditionalName() == null)
573 return;
574
575 Iterator it = rrset.rrs();
576 while (it.hasNext()) {
577 Record r = (Record) it.next();
578 Name name = r.getAdditionalName();
579 if (name != null)
580 names.add(name);
581 }
582}
583
584/**
585 * Adds all data from a Message into the Cache. Each record is added with
586 * the appropriate credibility, and negative answers are cached as such.
587 * @param in The Message to be added
588 * @return A SetResponse that reflects what would be returned from a cache
589 * lookup, or null if nothing useful could be cached from the message.
590 * @see Message
591 */
592public SetResponse
593addMessage(Message in) {
594 boolean isAuth = in.getHeader().getFlag(Flags.AA);
595 Record question = in.getQuestion();
596 Name qname;
597 Name curname;
598 int qtype;
599 int qclass;
600 int cred;
601 int rcode = in.getHeader().getRcode();
602 boolean completed = false;
603 RRset [] answers, auth, addl;
604 SetResponse response = null;
605 boolean verbose = Options.check("verbosecache");
606 HashSet additionalNames;
607
608 if ((rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) ||
609 question == null)
610 return null;
611
612 qname = question.getName();
613 qtype = question.getType();
614 qclass = question.getDClass();
615
616 curname = qname;
617
618 additionalNames = new HashSet();
619
620 answers = in.getSectionRRsets(Section.ANSWER);
621 for (int i = 0; i < answers.length; i++) {
622 if (answers[i].getDClass() != qclass)
623 continue;
624 int type = answers[i].getType();
625 Name name = answers[i].getName();
626 cred = getCred(Section.ANSWER, isAuth);
627 if ((type == qtype || qtype == Type.ANY) &&
628 name.equals(curname))
629 {
630 addRRset(answers[i], cred);
631 completed = true;
632 if (curname == qname) {
633 if (response == null)
634 response = new SetResponse(
635 SetResponse.SUCCESSFUL);
636 response.addRRset(answers[i]);
637 }
638 markAdditional(answers[i], additionalNames);
639 } else if (type == Type.CNAME && name.equals(curname)) {
640 CNAMERecord cname;
641 addRRset(answers[i], cred);
642 if (curname == qname)
643 response = new SetResponse(SetResponse.CNAME,
644 answers[i]);
645 cname = (CNAMERecord) answers[i].first();
646 curname = cname.getTarget();
647 } else if (type == Type.DNAME && curname.subdomain(name)) {
648 DNAMERecord dname;
649 addRRset(answers[i], cred);
650 if (curname == qname)
651 response = new SetResponse(SetResponse.DNAME,
652 answers[i]);
653 dname = (DNAMERecord) answers[i].first();
654 try {
655 curname = curname.fromDNAME(dname);
656 }
657 catch (NameTooLongException e) {
658 break;
659 }
660 }
661 }
662
663 auth = in.getSectionRRsets(Section.AUTHORITY);
664 RRset soa = null, ns = null;
665 for (int i = 0; i < auth.length; i++) {
666 if (auth[i].getType() == Type.SOA &&
667 curname.subdomain(auth[i].getName()))
668 soa = auth[i];
669 else if (auth[i].getType() == Type.NS &&
670 curname.subdomain(auth[i].getName()))
671 ns = auth[i];
672 }
673 if (!completed) {
674 /* This is a negative response or a referral. */
675 int cachetype = (rcode == Rcode.NXDOMAIN) ? 0 : qtype;
676 if (rcode == Rcode.NXDOMAIN || soa != null || ns == null) {
677 /* Negative response */
678 cred = getCred(Section.AUTHORITY, isAuth);
679 SOARecord soarec = null;
680 if (soa != null)
681 soarec = (SOARecord) soa.first();
682 addNegative(curname, cachetype, soarec, cred);
683 if (response == null) {
684 int responseType;
685 if (rcode == Rcode.NXDOMAIN)
686 responseType = SetResponse.NXDOMAIN;
687 else
688 responseType = SetResponse.NXRRSET;
689 response = SetResponse.ofType(responseType);
690 }
691 /* DNSSEC records are not cached. */
692 } else {
693 /* Referral response */
694 cred = getCred(Section.AUTHORITY, isAuth);
695 addRRset(ns, cred);
696 markAdditional(ns, additionalNames);
697 if (response == null)
698 response = new SetResponse(
699 SetResponse.DELEGATION,
700 ns);
701 }
702 } else if (rcode == Rcode.NOERROR && ns != null) {
703 /* Cache the NS set from a positive response. */
704 cred = getCred(Section.AUTHORITY, isAuth);
705 addRRset(ns, cred);
706 markAdditional(ns, additionalNames);
707 }
708
709 addl = in.getSectionRRsets(Section.ADDITIONAL);
710 for (int i = 0; i < addl.length; i++) {
711 int type = addl[i].getType();
712 if (type != Type.A && type != Type.AAAA && type != Type.A6)
713 continue;
714 Name name = addl[i].getName();
715 if (!additionalNames.contains(name))
716 continue;
717 cred = getCred(Section.ADDITIONAL, isAuth);
718 addRRset(addl[i], cred);
719 }
720 if (verbose)
721 System.out.println("addMessage: " + response);
722 return (response);
723}
724
725/**
726 * Flushes an RRset from the cache
727 * @param name The name of the records to be flushed
728 * @param type The type of the records to be flushed
729 * @see RRset
730 */
731public void
732flushSet(Name name, int type) {
733 removeElement(name, type);
734}
735
736/**
737 * Flushes all RRsets with a given name from the cache
738 * @param name The name of the records to be flushed
739 * @see RRset
740 */
741public void
742flushName(Name name) {
743 removeName(name);
744}
745
746/**
747 * Sets the maximum length of time that a negative response will be stored
748 * in this Cache. A negative value disables this feature (that is, sets
749 * no limit).
750 */
751public void
752setMaxNCache(int seconds) {
753 maxncache = seconds;
754}
755
756/**
757 * Gets the maximum length of time that a negative response will be stored
758 * in this Cache. A negative value indicates no limit.
759 */
760public int
761getMaxNCache() {
762 return maxncache;
763}
764
765/**
766 * Sets the maximum length of time that records will be stored in this
767 * Cache. A negative value disables this feature (that is, sets no limit).
768 */
769public void
770setMaxCache(int seconds) {
771 maxcache = seconds;
772}
773
774/**
775 * Gets the maximum length of time that records will be stored
776 * in this Cache. A negative value indicates no limit.
777 */
778public int
779getMaxCache() {
780 return maxcache;
781}
782
783/**
784 * Gets the current number of entries in the Cache, where an entry consists
785 * of all records with a specific Name.
786 */
787public int
788getSize() {
789 return data.size();
790}
791
792/**
793 * Gets the maximum number of entries in the Cache, where an entry consists
794 * of all records with a specific Name. A negative value is treated as an
795 * infinite limit.
796 */
797public int
798getMaxEntries() {
799 return data.getMaxSize();
800}
801
802/**
803 * Sets the maximum number of entries in the Cache, where an entry consists
804 * of all records with a specific Name. A negative value is treated as an
805 * infinite limit.
806 *
807 * Note that setting this to a value lower than the current number
808 * of entries will not cause the Cache to shrink immediately.
809 *
810 * The default maximum number of entries is 50000.
811 *
812 * @param entries The maximum number of entries in the Cache.
813 */
814public void
815setMaxEntries(int entries) {
816 data.setMaxSize(entries);
817}
818
819/**
820 * Returns the DNS class of this cache.
821 */
822public int
823getDClass() {
824 return dclass;
825}
826
827/**
828 * Returns the contents of the Cache as a string.
829 */
830public String
831toString() {
832 StringBuffer sb = new StringBuffer();
833 synchronized (this) {
834 Iterator it = data.values().iterator();
835 while (it.hasNext()) {
836 Element [] elements = allElements(it.next());
837 for (int i = 0; i < elements.length; i++) {
838 sb.append(elements[i]);
839 sb.append("\n");
840 }
841 }
842 }
843 return sb.toString();
844}
845
846}