Home | History | Annotate | Download | only in DNS
      1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) xbill.org)
      2 
      3 package org.xbill.DNS;
      4 
      5 import java.io.*;
      6 import 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 
     20 public class Cache {
     21 
     22 private interface Element {
     23 	public boolean expired();
     24 	public int compareCredibility(int cred);
     25 	public int getType();
     26 }
     27 
     28 private static int
     29 limitExpire(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 
     38 private 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 
     80 private 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 
    128 private 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 
    156 private CacheMap data;
    157 private int maxncache = -1;
    158 private int maxcache = -1;
    159 private int dclass;
    160 
    161 private 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  */
    169 public
    170 Cache(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  */
    179 public
    180 Cache() {
    181 	this(DClass.IN);
    182 }
    183 
    184 /**
    185  * Creates a Cache which initially contains all records in the specified file.
    186  */
    187 public
    188 Cache(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 
    196 private synchronized Object
    197 exactName(Name name) {
    198 	return data.get(name);
    199 }
    200 
    201 private synchronized void
    202 removeName(Name name) {
    203 	data.remove(name);
    204 }
    205 
    206 private synchronized Element []
    207 allElements(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 
    218 private synchronized Element
    219 oneElement(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 
    249 private synchronized Element
    250 findElement(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 
    257 private synchronized void
    258 addElement(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 
    288 private synchronized void
    289 removeElement(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. */
    314 public synchronized void
    315 clearCache() {
    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  */
    326 public synchronized void
    327 addRecord(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  */
    350 public synchronized void
    351 addRRset(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  */
    381 public synchronized void
    382 addNegative(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  */
    403 protected synchronized SetResponse
    404 lookup(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  */
    511 public SetResponse
    512 lookupRecords(Name name, int type, int minCred) {
    513 	return lookup(name, type, minCred);
    514 }
    515 
    516 private RRset []
    517 findRecords(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  */
    533 public RRset []
    534 findRecords(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  */
    546 public RRset []
    547 findAnyRecords(Name name, int type) {
    548 	return findRecords(name, type, Credibility.GLUE);
    549 }
    550 
    551 private final int
    552 getCred(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 
    569 private static void
    570 markAdditional(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  */
    592 public SetResponse
    593 addMessage(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  */
    731 public void
    732 flushSet(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  */
    741 public void
    742 flushName(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  */
    751 public void
    752 setMaxNCache(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  */
    760 public int
    761 getMaxNCache() {
    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  */
    769 public void
    770 setMaxCache(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  */
    778 public int
    779 getMaxCache() {
    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  */
    787 public int
    788 getSize() {
    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  */
    797 public int
    798 getMaxEntries() {
    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  */
    814 public void
    815 setMaxEntries(int entries) {
    816 	data.setMaxSize(entries);
    817 }
    818 
    819 /**
    820  * Returns the DNS class of this cache.
    821  */
    822 public int
    823 getDClass() {
    824 	return dclass;
    825 }
    826 
    827 /**
    828  * Returns the contents of the Cache as a string.
    829  */
    830 public String
    831 toString() {
    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 }
    847