struct timeval request_time;
dlink_node lru;
unsigned short locks;
+#if DNS_CNAME
+ unsigned short cname_wait;
+#endif
+
struct {
unsigned int negcached:1;
unsigned int fromhosts:1;
#else
static IDNSCB ipcacheHandleReply;
#endif
+static IPH ipcacheHandleCnameRecurse;
static int ipcacheExpiredEntry(ipcache_entry *);
#if USE_DNSSERVERS
static int ipcacheParse(ipcache_entry *, const char *buf);
{
hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
+#if DNS_CNAME
+ /* INET6 : should NOT be adding this entry until all CNAME have been received. */
+ assert(i->cname_wait == 0);
+#endif
+
if (NULL != e) {
/* avoid colission */
ipcache_entry *q = (ipcache_entry *) e;
- ipcacheRelease(q);
+#if DNS_CNAME
+ if (q == i) {
+ /* can occur with Multiple-depth CNAME Recursion if parent returned early with additional */
+ /* just need to drop from the hash without releasing actual memory */
+ ipcacheRelease(q, false);
+ } else
+#endif
+ ipcacheRelease(q);
}
hash_join(ip_table, &i->hash);
if (answers[k].type == RFC1035_TYPE_CNAME) {
cname_found=1;
IpcacheStats.rr_cname++;
+
+#if DNS_CNAME
+ debugs(14, 5, "ipcacheParse: " << name << " CNAME " << answers[k].rdata << " (checking destination: " << i << ").");
+ const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
+ if (res) {
+ na += res->count;
+ debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " already has " << res->count << " IPs cached.");
+ } else {
+ /* keep going on this, but flag the fact that we need to wait for a CNAME lookup to finish */
+ debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " has no IPs! Recursing.");
+ ipcache_nbgethostbyname(answers[k].rdata, ipcacheHandleCnameRecurse, new generic_cbdata(i) );
+ i->cname_wait++;
+ }
+#endif /* DNS_CNAME */
+
continue;
}
// otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
}
+
+#if DNS_CNAME
+ if (na == 0 && i->cname_wait >0 ) {
+ /* don't set any error message (yet). Allow recursion to do its work first. */
+ IpcacheStats.cname_only++;
+ return 0;
+ }
+#endif /* DNS_CNAME */
+
if (na == 0) {
debugs(14, 1, "ipcacheParse: No Address records in response to '" << name << "'");
i->error_message = xstrdup("No Address records");
j++;
#endif
}
+#if DNS_CNAME
+ else if (answers[k].type == RFC1035_TYPE_CNAME) {
+ debugs(14, 3, "ipcacheParse: " << name << " #x CNAME " << answers[k].rdata);
+ const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
+ if (res) {
+ /* NP: the results of *that* query need to be integrated in place of the CNAME */
+ /* Ideally we should also integrate the min TTL of the above IPA's into ttl. */
+ for (int l = 0; l < res->count; l++, j++) {
+ i->addrs.in_addrs[j] = res->in_addrs[l];
+ debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
+ }
+ } else {
+ debugs(14, 9, "ipcacheParse: " << answers[k].rdata << " (CNAME) waiting on A/AAAA records.");
+ }
+ }
+#endif /* DNS_CNAME */
+
if (ttl == 0 || (int) answers[k].ttl < ttl)
ttl = answers[k].ttl;
}
i->flags.negcached = 0;
- return i->addrs.count;
+#if DNS_CNAME
+ /* SPECIAL CASE: may get here IFF CNAME received with Additional records */
+ /* reurn 0/'wait for further details' value. */
+ /* NP: 'No DNS Results' is a return -1 +msg */
+ if (i->cname_wait)
+ return 0;
+ else
+#endif /* DNS_CNAME */
+ return i->addrs.count;
}
#endif
IpcacheStats.misses++;
if (flags & IP_LOOKUP_IF_MISS)
- ipcache_nbgethostbyname(name, NULL, NULL);
+ ipcache_nbgethostbyname(name, ipcacheHandleCnameRecurse, NULL);
return NULL;
}
}
}
+#if DNS_CNAME
+/**
+ * Takes two Ip::Address arrays and merges them into a single array
+ * which is allocated dynamically to fit the number of unique addresses
+ *
+ \param aaddrs One list to merge
+ \param alen Size of list aaddrs
+ \param baddrs Other list to merge
+ \param alen Size of list baddrs
+ \param out Combined list of unique addresses (sorted with IPv6 first in IPv6-mode)
+ \param outlen Size of list out
+ */
+void
+ipcacheMergeIPLists(const Ip::Address *aaddrs, const int alen,
+ const Ip::Address *baddrs, const int blen,
+ Ip::Address **out, int &outlen )
+{
+ int fc=0, t=0, c=0;
+
+ Ip::Address const *ip4ptrs[255];
+#if USE_IPV6
+ Ip::Address const *ip6ptrs[255];
+#endif
+ int num_ip4 = 0;
+ int num_ip6 = 0;
+
+ memset(ip4ptrs, 0, sizeof(Ip::Address*)*255);
+#if USE_IPV6
+ memset(ip6ptrs, 0, sizeof(Ip::Address*)*255);
+#endif
+
+ // for each unique address in list A - grab ptr
+ for (t = 0; t < alen; t++) {
+ if (aaddrs[t].IsIPv4()) {
+ // check against IPv4 pruned list
+ for (c = 0; c <= num_ip4; c++) {
+ if (ip4ptrs[c] && aaddrs[t] == *(ip4ptrs[c]) ) break; // duplicate.
+ }
+ if (c > num_ip4) {
+ ip4ptrs[num_ip4] = &aaddrs[t];
+ num_ip4++;
+ }
+ }
+#if USE_IPV6
+ else if (aaddrs[t].IsIPv6()) {
+ debugs(14,8, HERE << "A[" << t << "]=IPv6 " << aaddrs[t]);
+ // check against IPv6 pruned list
+ for (c = 0; c <= num_ip6; c++) {
+ if (ip6ptrs[c] && aaddrs[t] == *ip6ptrs[c]) break; // duplicate.
+ }
+ if (c > num_ip6) {
+ ip6ptrs[num_ip6] = &aaddrs[t];
+ num_ip6++;
+ }
+ }
+#endif
+ }
+
+ // for each unique address in list B - grab ptr
+ for (t = 0; t < blen; t++) {
+ if (baddrs[t].IsIPv4()) {
+ // check against IPv4 pruned list
+ for (c = 0; c <= num_ip4; c++) {
+ if (ip4ptrs[c] && baddrs[t] == *ip4ptrs[c]) break; // duplicate.
+ }
+ if (c > num_ip4) {
+ ip4ptrs[num_ip4] = &baddrs[t];
+ num_ip4++;
+ }
+ }
+#if USE_IPV6
+ else if (baddrs[t].IsIPv6()) {
+ // check against IPv6 pruned list
+ for (c = 0; c <= num_ip6; c++) {
+ if (ip6ptrs[c] && baddrs[t] == *ip6ptrs[c]) break; // duplicate.
+ }
+ if (c > num_ip6) {
+ ip6ptrs[num_ip6] = &baddrs[t];
+ num_ip6++;
+ }
+ }
+#endif
+ }
+
+ fc = num_ip6 + num_ip4;
+
+ assert(fc > 0);
+
+ debugs(14, 5, "ipcacheMergeIPLists: Merge " << alen << "+" << blen << " into " << fc << " unique IPs.");
+
+ // copy the old IPs into the new list buffer.
+ (*out) = static_cast<Ip::Address*>(xcalloc(fc, sizeof(Ip::Address)));
+ outlen=0;
+
+ assert(out != NULL);
+
+#if USE_IPV6
+ /* IPv6 are preferred (tried first) over IPv4 */
+
+ for (int l = 0; outlen < num_ip6; l++, outlen++) {
+ (*out)[outlen] = *ip6ptrs[l];
+ debugs(14, 5, "ipcacheMergeIPLists: #" << outlen << " " << (*out)[outlen] );
+ }
+#endif /* USE_IPV6 */
+
+ for (int l = 0; outlen < num_ip4; l++, outlen++) {
+ (*out)[outlen] = *ip4ptrs[l];
+ debugs(14, 5, "ipcacheMergeIPLists: #" << outlen << " " << (*out)[outlen] );
+ }
+
+ assert(outlen == fc); // otherwise something broke badly!
+}
+#endif /* DNS_CNAME */
+
+/// \ingroup IPCacheInternal
+/// Callback.
+static void
+ipcacheHandleCnameRecurse(const ipcache_addrs *addrs, const DnsLookupDetails &, void *cbdata)
+{
+#if DNS_CNAME
+ ipcache_entry *i = NULL;
+ char *pname = NULL;
+ Ip::Address *tmpbuf = NULL;
+ int fc = 0;
+ int ttl = 0;
+ generic_cbdata* gcb = (generic_cbdata*)cbdata;
+ // count of addrs at parent and child (REQ as .count is a char type!)
+ int ccount = 0, pcount = 0;
+
+ debugs(14, 5, "ipcacheHandleCnameRecurse: Handling basic A/AAAA response.");
+
+ /* IFF no CNAME recursion being processed. do nothing. */
+ if (cbdata == NULL)
+ return;
+
+ gcb->unwrap(&i);
+ assert(i != NULL);
+
+ // make sure we are actualy waiting for a CNAME callback to be run.
+ assert(i->cname_wait > 0);
+ // count this event. its being handled.
+ i->cname_wait--;
+
+ pname = (char*)i->hash.key;
+ assert(pname != NULL);
+
+ debugs(14, 5, "ipcacheHandleCnameRecurse: Handling CNAME recursion. CBDATA('" << gcb->data << "')='" << pname << "' -> " << std::hex << i);
+
+ if (i == NULL) {
+ return; // Parent has expired. Don't merge, just leave for future Ref:
+ }
+
+ /* IFF addrs is NULL (Usually an Error or Timeout occured on lookup.) */
+ /* Ignore it and HOPE that we got some Additional records to use. */
+ if (addrs == NULL)
+ return;
+
+ ccount = (0+ addrs->count);
+ pcount = (0+ i->addrs.count);
+ ttl = i->expires;
+
+ /* IFF no CNAME results. do none of the processing BUT finish anyway. */
+ if (addrs) {
+
+ debugs(14, 5, "ipcacheHandleCnameRecurse: Merge IP Lists for " << pname << " (" << pcount << "+" << ccount << ")");
+
+ /* add new IP records to entry */
+ tmpbuf = i->addrs.in_addrs;
+ i->addrs.in_addrs = NULL;
+ ipcacheMergeIPLists(tmpbuf, pcount, addrs->in_addrs, ccount, &(i->addrs.in_addrs), fc);
+ debugs(14,8, HERE << "in=" << tmpbuf << ", out=" << i->addrs.in_addrs );
+ assert( (pcount>0 ? tmpbuf!=NULL : tmpbuf==NULL) );
+ safe_free(tmpbuf);
+
+ if ( pcount > 0) {
+ /* IFF the parent initial lookup was given Additional records with A */
+ // clear the 'bad IP mask'
+ safe_free(i->addrs.bad_mask);
+ }
+ // create a new bad IP mask to fit the new size needed.
+ if (fc > 0) {
+ i->addrs.bad_mask = (unsigned char*)xcalloc(fc, sizeof(unsigned char));
+ memset(i->addrs.bad_mask, 0, sizeof(unsigned char)*fc);
+ }
+
+ if (fc < 256)
+ i->addrs.count = (unsigned char) fc;
+ else
+ i->addrs.count = 255;
+
+ if (ttl == 0 || ttl > Config.positiveDnsTtl)
+ ttl = Config.positiveDnsTtl;
+
+ if (ttl < Config.negativeDnsTtl)
+ ttl = Config.negativeDnsTtl;
+
+ i->expires = squid_curtime + ttl;
+
+ i->flags.negcached = 0;
+
+ i->addrs.cur = 0;
+
+ i->addrs.badcount = 0;
+ }
+
+ if (fc == 0) {
+ i->error_message = xstrdup("No DNS Records");
+ }
+
+ /* finish the lookup we were doing on parent when we got side-tracked for CNAME loop */
+ if (i->cname_wait == 0) {
+ ipcacheAddEntry(i);
+ ipcacheCallback(i, i->age()); // age since i creation, includes CNAMEs
+ }
+ // else still more CNAME to be found.
+#endif /* DNS_CNAME */
+}
+
/// \ingroup IPCacheAPI
void
ipcacheInvalidate(const char *name)