/*************************************************************************** resolver.cpp - resolver functions ------------------- begin : do dec 26 2002 copyright : (C) 2002 by Meilof email : meilof@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "resolver.h" #include "stdquery.h" #include "recursive.h" #include "forward.h" #include "configuration.h" #include "lib.h" #include "auth_mem.h" int n_cache_doms = 0, delta_n_cache_doms = 0, n_cache_hits = 0, delta_n_cache_hits = 0, n_cache_misses = 0, delta_n_cache_misses = 0; /* cleanup */ void cache_cleanup() { int res; res = max_cache_items * 100 / 95; #ifdef DEBUG int n = 0; ZoneDomain *test = zd_first; while (test) { n++; test = test->i.cache.next; } printf("Domain names: %d<>%d\n", n_cache_doms, n); #endif while (n_cache_doms > max_cache_items && zd_first) { if (zd_first->subitems.nitems) zd_first->cache_top(); else zd_first->removeme(); } } /* ----- cache and zone helper functions (should be moved to zones.cpp?) --- */ void add_authset_to_cache(domainname &nam, RrSet *set) { if (!set) return; domainname root = ""; ZoneDomain *dom = create_domainname(cache_root_domain, root, nam, ZF_DONOTREMOVE); int x; RrSet *nset; if (dom) { dom->remove_rrset(set->type); nset = dom->create_rrset(set->type); for (x = 0; x < set->rrs.count; x++) nset->rrs.add(new RrData(set->rrs[x])); nset->ttl = time(NULL) + set->ttl; } } void add_glue_to_cache(domainname &root, ZoneDomain *zdom) { RrSet *set = zdom->get_rrset(DNS_TYPE_NS); int x; domainname dom; ZoneDomain *sub; if (!set) return; add_authset_to_cache(root, set); for (x = 0; x < set->rrs.count; x++) { dom = domainname(true, set->rrs[x]->rdata); if (dom >= root) { sub = lookup_domainname(zdom, root, dom); if (sub) { add_authset_to_cache(dom, sub->get_rrset(DNS_TYPE_A)); add_authset_to_cache(dom, sub->get_rrset(DNS_TYPE_AAAA)); add_authset_to_cache(dom, sub->get_rrset(DNS_TYPE_A6)); } } } } void add_set_to_message(domainname &dname, RrSet *set, stl_list(DnsRR) §ion, bool check) { int x, t, z; uint32_t ttl; RrData *dat; if (!set) return; if (check) if (set->expired()) return; if (set->ttl <= TTL_MAX_AUTHORITIVE) { ttl = set->ttl; } else { ttl = set->ttl - time(NULL); if (ttl > TTL_NOVAL) ttl = 0; /* apparently, negative ttl */ } z = set->rrs.count; if (z == 0) return; t = possimplerandom(); t = t % z; for (x = t; x < (t + z); x++) { dat = set->rrs[x%z]; section.push_back(DnsRR(dname, set->type, CLASS_IN, ttl, dat->rdlength, dat->rdata)); } } void add_sets_to_message(domainname &dom, ZoneDomain *zdom, uint16_t type, stl_list(DnsRR) &sec) { switch (type) { case QTYPE_ANY: for (int x = 0; x < zdom->rrsets.count; x++) add_set_to_message(dom, zdom->rrsets[x], sec, true); break; case QTYPE_MAILA: add_set_to_message(dom, zdom->get_rrset(DNS_TYPE_MF), sec, true); add_set_to_message(dom, zdom->get_rrset(DNS_TYPE_MD), sec, true); case QTYPE_MAILB: add_set_to_message(dom, zdom->get_rrset(DNS_TYPE_MB), sec, true); add_set_to_message(dom, zdom->get_rrset(DNS_TYPE_MG), sec, true); add_set_to_message(dom, zdom->get_rrset(DNS_TYPE_MR), sec, true); break; default: add_set_to_message(dom, zdom->get_rrset(type), sec, true); } } /* add delegation information from cache */ void add_cache_delegation(domainname &dname, DnsMessage *msg, uint16_t type) { int nl = dname.nlabels(), t; stl_string label; ZoneDomain *dom = cache_root_domain; RrSet *nsset = dom->get_rrset(type), *set; domainname nsdname; for (t = nl - 2; t >= 0; t--) { label = dname.label(t); dom = dom->subitems.get_item(label.c_str()); if (!dom) break; if ((set = dom->get_rrset(type)) != NULL && set->rrs.count != 0) { nsset = set; nsdname = dname.from(t); } } if (!has_rrset(msg->authority, nsdname, type) && !has_rrset(msg->answers, nsdname, type)) add_set_to_message(nsdname, nsset, msg->authority); } Zone *best_zone(Zone *zn, domainname &znroot, Zone *zn2, domainname &dom2) { if (zn && zn2) { if (znroot.nlabels() < dom2.nlabels()) return zn2; else return zn; } else if (zn) { return zn; } else if (zn2) { return zn2; } else { return NULL; } } bool lookup_adresses_from_local_data(pending_query *qinfo, domainname &dom, stl_slist(_addr)& addresses) { stl_list(DnsRR) section; lookup_from_local_data(dom, DNS_TYPE_A, section, qinfo); lookup_from_local_data(dom, DNS_TYPE_AAAA, section, qinfo); stl_list(DnsRR)::iterator it = section.begin(); _addr a; bool ret = false; while (it != section.end()) { if (it->TYPE == DNS_TYPE_A) { getaddress_ip4(&a, it->RDATA, DNS_PORT); ret = true; addresses.push_front(a); #ifdef HAVE_IPV6 } else if (it->TYPE == DNS_TYPE_AAAA) { getaddress_ip6(&a, it->RDATA, DNS_PORT); ret = true; addresses.push_front(a); #endif } it++; } return ret; } /* looks up data for the additional section from local data */ void lookup_from_local_data(domainname &dom, uint16_t qtype, stl_list(DnsRR) §ion, pending_query *qinfo) { /* at first, try the authoritative side of things */ Zone *z, *z2 = NULL, *z3; domainname zroot, dom2; ZoneDomain *zdom; z = lookup_authoritative_zone(dom, &zroot, false); z2 = lookup_dyn_zone(dom, &dom2); try { z3 = best_zone(z, zroot, z2, dom2); if (z3 == z2) zroot = dom2; if (z3 && z3->additional_lookup(dom, qtype, section, zroot, qinfo)) { if (z2) delete z2; return; } } catch (PException p) { } if (z2) delete z2; /* now try the cache */ zdom = lookup_domainname(cache_root_domain, root_domain_name, dom); if (zdom) add_sets_to_message(dom, zdom, qtype, section); } /* ----- cache lookup functions -------------------------------------------- */ DnsRR *get_soa_rr(domainname &dom, stl_list(DnsRR) &ls) { stl_list(DnsRR)::iterator it = ls.begin(); while (it != ls.end()) { if (it->TYPE == DNS_TYPE_SOA && dom >= it->NAME) return &*it; it++; } return NULL; } /* ----- answer query from cache ------------------------------------------- */ _cache_ret query_cache(domainname &quest, uint16_t rrtype, DnsMessage *a, RrSet **retset) { ZoneDomain *zdom; RrSet *set; unsigned int sz; if (retset) *retset = NULL; zdom = lookup_domainname(cache_root_domain, root_domain_name, quest); if (zdom) { if ((set = zdom->get_rrset(DNS_TYPE_CNAME)) != NULL && rrtype != DNS_TYPE_CNAME && rrtype != QTYPE_ANY && set->rrs.count == 1) { /* yay! we have a CNAME */ add_set_to_message(quest, set, a->answers); return RET_CNAME; } if (zdom->subitems.isempty() && zdom->rrsets.count == 0 && (uint32_t)time(NULL) > zdom->ext.ncache_time) { /* this domain name has just expired. put it on top of the removal queue */ zdom->cache_bottom(); } else if ((uint32_t)time(NULL) <= zdom->ext.ncache_time) { /* check whether we have a cached NXDOMAIN */ a->RCODE = RCODE_NXDOMAIN; /* add SOA rr */ if (a->authority.empty()) add_cache_delegation(quest, a, DNS_TYPE_SOA); return RET_NOERROR; } else { if (rrtype == QTYPE_ANY || rrtype == QTYPE_MAILB || rrtype == QTYPE_MAILA) { /* for wildcard queries, we don't do anything additional */ sz = a->answers.size(); add_sets_to_message(quest, zdom, rrtype, a->answers); if (sz != a->answers.size()) { if (a->authority.empty()) add_cache_delegation(quest, a); return RET_NOERROR; } } else { /* may have data (including cached NODATA) */ set = zdom->get_rrset(rrtype); if (set) { if (retset) *retset = set; if (set->rrs.count == 0) { if (a->authority.empty()) add_cache_delegation(quest, a, DNS_TYPE_SOA); } else { add_set_to_message(quest, set, a->answers); if (a->authority.empty()) add_cache_delegation(quest, a); } return RET_NOERROR; } } } } return RET_NOINFO; } /* ----- query entry point ------------------------------------------------- */ bool cache_lookup(pending_query *queryinfo, DnsMessage *a, domainname &quest, uint16_t rrtype) { if (!cache_root_domain->get_rrset(DNS_TYPE_NS)) { // pthread_mutex_lock(&m_auth_zones); try { if (!auth_root_zone->is_placeholder && (auth_root_zone->type == Z_PRIMARY || auth_root_zone->type == Z_SECONDARY)) { /* that's because we're authoritative for '.'! */ add_glue_to_cache(root_domain_name, ((AuthMemZone *)auth_root_zone)->rootdomain); } } catch (PException p) { } // pthread_mutex_unlock(&m_auth_zones); if (!cache_root_domain->get_rrset(DNS_TYPE_NS)) { a->RCODE = RCODE_SRVFAIL; return false; } } #ifdef DEBUG printf("Cache check: {%s,%d}\n", quest.tocstr(), str_qtype(rrtype).c_str()); #endif _cache_ret ret = query_cache(quest, rrtype, a); if (ret == RET_NOERROR) { n_cache_hits++; delta_n_cache_hits++; #ifdef DEBUG printf("Cache hit: {%s,%d}\n", quest.tocstr(), str_qtype(rrtype).c_str()); #endif return false; } else if (ret == RET_CNAME) { n_cache_hits++; delta_n_cache_hits++; #ifdef DEBUG printf("Cache hit: {%s,%d}\n", quest.tocstr(), str_qtype(rrtype).c_str()); #endif return true; } /* ----- handle queries we don't want to recurse for --------------------- */ if (!a->RD || !a->RA) { /* we're not doing any recursion here, just query the cache */ add_cache_delegation(quest, a); return false; } /* report cache miss */ n_cache_misses++; delta_n_cache_misses++; /* ---- find out whether to forward or to resolve ------------------------ */ /* find out the values of the settings we want to know */ CacheZone *zn = cache_root_zone; stl_slist(_addr) *forwarders = cache_root_zone->forwarders; int nl = quest.nlabels(); stl_string label; for (int t = nl - 2; t >= 0; t--) { label = quest.label(t); zn = (CacheZone *)zn->subitems.get_item(label.c_str()); if (!zn) break; if (!zn->is_placeholder && zn->forwarders) forwarders = zn->forwarders; } if (forwarders == NULL || forwarders->size() == 0) { /* ----- do lookup ourselves ------------------------------------------- */ int patience = resolv_patience; /* at first, see if we can answer it ourselves */ _sysquery_result res = sysquery(queryinfo, quest, rrtype, patience); if (res == srvfail) { // printf("Sysquery failed!\n"); a->RCODE = RCODE_SRVFAIL; } else { // printf("Sysquery result: %d\n", res); _cache_ret ret2 = query_cache(quest, rrtype, a); // printf("Queried caceh: %d\n", ret2); if (ret2 == RET_NOERROR) return false; else if (ret2 == RET_CNAME) { #ifdef DEBUG printf("Returning CNAME\n"); #endif return true; } } return false; } else { /* ----- do forwarding ------------------------------------------------- */ forward_query(a, quest, rrtype, forwarders); return false; } return false; }