freebsd-nq/contrib/bind/named/ns_forw.c
Peter Wemm d6da9453b6 Take #2. Import bind-4.9.4-P1 into the intended directory!
This has most of the non-essential stuff removed (ie: what is not built)

bmake glue to follow.
1996-08-29 19:20:22 +00:00

1095 lines
30 KiB
C

#if !defined(lint) && !defined(SABER)
static char sccsid[] = "@(#)ns_forw.c 4.32 (Berkeley) 3/3/91";
static char rcsid[] = "$Id: ns_forw.c,v 8.14 1996/08/05 08:31:30 vixie Exp $";
#endif /* not lint */
/*
* ++Copyright++ 1986
* -
* Copyright (c) 1986
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* -
* Portions Copyright (c) 1993 by Digital Equipment Corporation.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies, and that
* the name of Digital Equipment Corporation not be used in advertising or
* publicity pertaining to distribution of the document or software without
* specific, written prior permission.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
* CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
* -
* --Copyright--
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <syslog.h>
#include <resolv.h>
#include <stdio.h>
#include <errno.h>
#include "named.h"
/*
* Forward the query to get the answer since its not in the database.
* Returns FW_OK if a request struct is allocated and the query sent.
* Returns FW_DUP if this is a duplicate of a pending request.
* Returns FW_NOSERVER if there were no addresses for the nameservers.
* Returns FW_SERVFAIL on malloc error or if asked to do something
* dangerous, such as fwd to ourselves or fwd to the host that asked us.
*
* (no action is taken on errors and qpp is not filled in.)
*/
int
ns_forw(nsp, msg, msglen, fp, qsp, dfd, qpp, dname, np)
struct databuf *nsp[];
u_char *msg;
int msglen;
struct sockaddr_in *fp;
struct qstream *qsp;
int dfd;
struct qinfo **qpp;
char *dname;
struct namebuf *np;
{
register struct qinfo *qp;
struct sockaddr_in *nsa;
HEADER *hp;
u_int16_t id;
int n;
dprintf(3, (ddt, "ns_forw()\n"));
hp = (HEADER *) msg;
id = hp->id;
/* Look at them all */
for (qp = nsqhead; qp != QINFO_NULL; qp = qp->q_link) {
if (qp->q_id == id &&
bcmp((char *)&qp->q_from, fp, sizeof(qp->q_from)) == 0 &&
((qp->q_cmsglen == 0 && qp->q_msglen == msglen &&
bcmp((char *)qp->q_msg+2, msg+2, msglen-2) == 0) ||
(qp->q_cmsglen == msglen &&
bcmp((char *)qp->q_cmsg+2, msg+2, msglen-2) == 0))) {
dprintf(3, (ddt,
"forw: dropped DUP id=%d\n", ntohs(id)));
#ifdef XSTATS
nameserIncr(fp->sin_addr, nssRcvdDupQ);
#endif
return (FW_DUP);
}
}
qp = qnew();
#if defined(LAME_DELEGATION) || defined(VALIDATE)
getname(np, qp->q_domain, sizeof qp->q_domain);
#endif
qp->q_from = *fp; /* nslookup wants to know this */
if ((n = nslookup(nsp, qp, dname, "ns_forw")) < 0) {
dprintf(2, (ddt, "forw: nslookup reports danger\n"));
qfree(qp);
return (FW_SERVFAIL);
} else if (n == 0 && !fwdtab) {
dprintf(2, (ddt, "forw: no nameservers found\n"));
qfree(qp);
return (FW_NOSERVER);
}
qp->q_stream = qsp;
qp->q_curaddr = 0;
qp->q_fwd = fwdtab;
qp->q_dfd = dfd;
qp->q_id = id;
qp->q_expire = tt.tv_sec + RETRY_TIMEOUT*2;
hp->id = qp->q_nsid = htons(nsid_next());
hp->ancount = htons(0);
hp->nscount = htons(0);
hp->arcount = htons(0);
if ((qp->q_msg = (u_char *)malloc((unsigned)msglen)) == NULL) {
syslog(LOG_NOTICE, "forw: malloc: %m");
qfree(qp);
return (FW_SERVFAIL);
}
bcopy(msg, qp->q_msg, qp->q_msglen = msglen);
if (!qp->q_fwd) {
hp->rd = 0;
qp->q_addr[0].stime = tt;
}
#ifdef SLAVE_FORWARD
if (forward_only)
schedretry(qp, (time_t)slave_retry);
else
#endif /* SLAVE_FORWARD */
schedretry(qp, qp->q_fwd ?(2*RETRYBASE) :retrytime(qp));
nsa = Q_NEXTADDR(qp, 0);
dprintf(1, (ddt,
"forw: forw -> [%s].%d ds=%d nsid=%d id=%d %dms retry %dsec\n",
inet_ntoa(nsa->sin_addr),
ntohs(nsa->sin_port), ds,
ntohs(qp->q_nsid), ntohs(qp->q_id),
(qp->q_addr[0].nsdata != NULL)
? qp->q_addr[0].nsdata->d_nstime
: -1,
(int)(qp->q_time - tt.tv_sec)));
#ifdef DEBUG
if (debug >= 10)
fp_nquery(msg, msglen, ddt);
#endif
if (sendto(ds, (char *)msg, msglen, 0, (struct sockaddr *)nsa,
sizeof(struct sockaddr_in)) < 0) {
if (!haveComplained((char*)nsa->sin_addr.s_addr, sendtoStr))
syslog(LOG_INFO, "ns_forw: sendto([%s].%d): %m",
inet_ntoa(nsa->sin_addr), ntohs(nsa->sin_port));
nameserIncr(nsa->sin_addr, nssSendtoErr);
}
#ifdef XSTATS
nameserIncr(fp->sin_addr, nssRcvdFwdQ);
#endif
nameserIncr(nsa->sin_addr, nssSentFwdQ);
if (qpp)
*qpp = qp;
hp->rd = 1;
return (0);
}
/* struct qdatagram *
* aIsUs(addr)
* scan the datagramq (our list of interface addresses) for "addr"
* returns:
* pointer to qdatagram entry or NULL if no match is found
* notes:
* INADDR_ANY ([0.0.0.0]) is on the datagramq, so it's considered "us"
* author:
* Paul Vixie (DECWRL) April 1991
*/
struct qdatagram *
aIsUs(addr)
struct in_addr addr;
{
struct qdatagram *dqp;
for (dqp = datagramq; dqp != QDATAGRAM_NULL; dqp = dqp->dq_next) {
if (addr.s_addr == dqp->dq_addr.s_addr) {
return dqp;
}
}
return NULL;
}
/* haveComplained(tag1, tag2)
* check to see if we have complained about (tag1,tag2) recently
* (note that these are declared as pointers but are never deref'd)
* returns:
* boolean: have we complained recently?
* side-effects:
* outdated complaint records removed from our static list
* author:
* Paul Vixie (DECWRL) April 1991
*/
int
haveComplained(tag1, tag2)
const char *tag1, *tag2;
{
struct complaint {
const char *tag1, *tag2;
time_t expire;
struct complaint *next;
};
static struct complaint *List = NULL;
struct complaint *cur, *next, *prev;
int r = 0;
for (cur = List, prev = NULL; cur; prev = cur, cur = next) {
next = cur->next;
if (tt.tv_sec > cur->expire) {
if (prev)
prev->next = next;
else
List = next;
free((char*) cur);
cur = prev;
} else if ((tag1 == cur->tag1) && (tag2 == cur->tag2)) {
r++;
}
}
if (!r) {
cur = (struct complaint *)malloc(sizeof(struct complaint));
if (cur) {
cur->tag1 = tag1;
cur->tag2 = tag2;
cur->expire = tt.tv_sec + INIT_REFRESH; /* "10:00" */
cur->next = NULL;
if (prev)
prev->next = cur;
else
List = cur;
}
}
return (r);
}
/* void
* nslookupComplain(sysloginfo, queryname, complaint, dname, a_rr)
* Issue a complaint about a dangerous situation found by nslookup().
* params:
* sysloginfo is a string identifying the complainant.
* queryname is the domain name associated with the problem.
* complaint is a string describing what is wrong.
* dname and a_rr are the problematic other name server.
*/
static void
nslookupComplain(sysloginfo, queryname, complaint, dname, a_rr, nsdp)
const char *sysloginfo, *queryname, *complaint, *dname;
const struct databuf *a_rr, *nsdp;
{
#ifdef STATS
char nsbuf[20];
char abuf[20];
#endif
char *a, *ns;
const char *a_type;
int print_a;
dprintf(2, (ddt, "NS '%s' %s\n", dname, complaint));
if (sysloginfo && queryname && !haveComplained(queryname, complaint))
{
char buf[999];
a = ns = (char *)NULL;
print_a = (a_rr->d_type == T_A);
a_type = p_type(a_rr->d_type);
#ifdef NCACHE
if (a_rr->d_rcode) {
print_a = 0;
switch(a_rr->d_rcode) {
case NXDOMAIN:
a_type = "NXDOMAIN";
break;
case NOERROR_NODATA:
a_type = "NODATA";
break;
}
}
#endif
#ifdef STATS
if (nsdp) {
if (nsdp->d_ns) {
strcpy(nsbuf, inet_ntoa(nsdp->d_ns->addr));
ns = nsbuf;
} else {
ns = zones[nsdp->d_zone].z_origin;
}
}
if (a_rr->d_ns) {
strcpy(abuf, inet_ntoa(a_rr->d_ns->addr));
a = abuf;
} else {
a = zones[a_rr->d_zone].z_origin;
}
#endif
/* syslog only takes 5 params */
if ( a != NULL || ns != NULL)
sprintf(buf, "%s: query(%s) %s (%s:%s) learnt (%s=%s:NS=%s)",
sysloginfo, queryname,
complaint, dname,
print_a ?
inet_ntoa(data_inaddr(a_rr->d_data)) : "",
a_type,
a ? a : "<Not Available>",
ns ? ns : "<Not Available>" );
else
sprintf(buf, "%s: query(%s) %s (%s:%s)",
sysloginfo, queryname,
complaint, dname,
print_a ?
inet_ntoa(data_inaddr(a_rr->d_data)) : "");
syslog(LOG_INFO, buf);
}
}
/*
* nslookup(nsp, qp, syslogdname, sysloginfo)
* Lookup the address for each nameserver in `nsp' and add it to
* the list saved in the qinfo structure pointed to by `qp'.
* Omits information about nameservers that we shouldn't ask.
* Detects the following dangerous operations:
* One of the A records for one of the nameservers in nsp
* refers to the address of one of our own interfaces;
* One of the A records refers to the nameserver port on
* the host that asked us this question.
* returns: the number of addresses added, or -1 if a dangerous operation
* is detected.
* side effects:
* if a dangerous situation is detected and (syslogdname && sysloginfo),
* calls syslog.
*/
int
nslookup(nsp, qp, syslogdname, sysloginfo)
struct databuf *nsp[];
register struct qinfo *qp;
const char *syslogdname;
const char *sysloginfo;
{
register struct namebuf *np;
register struct databuf *dp, *nsdp;
register struct qserv *qs;
register int n;
register unsigned int i;
struct hashbuf *tmphtp;
char *dname;
const char *fname;
int oldn, naddr, class, found_arr, potential_ns;
time_t curtime;
dprintf(3, (ddt, "nslookup(nsp=0x%lx, qp=0x%lx, \"%s\")\n",
(u_long)nsp, (u_long)qp, syslogdname));
potential_ns = 0;
naddr = n = qp->q_naddr;
curtime = (u_long) tt.tv_sec;
while ((nsdp = *nsp++) != NULL) {
class = nsdp->d_class;
dname = (char *)nsdp->d_data;
dprintf(3, (ddt, "nslookup: NS \"%s\" c=%d t=%d (%#lx)\n",
dname, class, nsdp->d_type,
(u_long)nsdp->d_flags));
/* don't put in servers we have tried */
for (i = 0; i < qp->q_nusedns; i++) {
if (qp->q_usedns[i] == nsdp) {
dprintf(2, (ddt,
"skipping used NS w/name %s\n",
nsdp->d_data));
goto skipserver;
}
}
tmphtp = ((nsdp->d_flags & DB_F_HINT) ?fcachetab :hashtab);
np = nlookup(dname, &tmphtp, &fname, 1);
if (np == NULL) {
dprintf(3, (ddt, "%s: not found %s %lx\n",
dname, fname, (u_long)np));
found_arr = 0;
goto need_sysquery;
}
if (fname != dname) {
if (findMyZone(np, class) == DB_Z_CACHE) {
/*
* lifted from findMyZone()
* We really need to know if the NS
* is the bottom of one of our zones
* to see if we've got missing glue
*/
for (; np; np = np_parent(np))
for (dp = np->n_data; dp; dp=dp->d_next)
if (match(dp, class, T_NS)) {
#ifdef NCACHE
if (dp->d_rcode)
break;
#endif
if (dp->d_zone) {
static char *complaint =
"Glue A RR missing";
nslookupComplain(sysloginfo,
syslogdname,
complaint,
dname, dp,
nsdp);
goto skipserver;
} else {
found_arr = 0;
goto need_sysquery;
}
}
/* shouldn't happen, but ... */
found_arr = 0;
goto need_sysquery;
} else {
static char *complaint =
"Authoritative A RR missing";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
}
found_arr = 0;
oldn = n;
/* look for name server addresses */
for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
struct in_addr nsa;
if (dp->d_type == T_CNAME && dp->d_class == class) {
static char *complaint = "NS points to CNAME";
#ifdef NCACHE
if (dp->d_rcode)
continue;
#endif
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
goto skipserver;
}
if (dp->d_type != T_A || dp->d_class != class)
continue;
#ifdef NCACHE
if (dp->d_rcode) {
static char *complaint =
"A RR negative cache entry";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
goto skipserver;
}
#endif
if (data_inaddr(dp->d_data).s_addr == INADDR_ANY) {
static char *complaint = "Bogus (0.0.0.0) A RR";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
#ifdef INADDR_LOOPBACK
if (ntohl(data_inaddr(dp->d_data).s_addr) ==
INADDR_LOOPBACK) {
static char *complaint = "Bogus LOOPBACK A RR";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
#endif
#ifdef INADDR_BROADCAST
if (ntohl(data_inaddr(dp->d_data).s_addr) ==
INADDR_BROADCAST) {
static char *complaint = "Bogus BROADCAST A RR";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
#endif
#ifdef IN_MULTICAST
if (IN_MULTICAST(ntohl(data_inaddr(dp->d_data).s_addr))) {
static char *complaint = "Bogus MULTICAST A RR";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
#endif
/*
* Don't use records that may become invalid to
* reference later when we do the rtt computation.
* Never delete our safety-belt information!
*/
if ((dp->d_zone == 0) &&
#ifdef DATUMREFCNT
(dp->d_ttl < curtime) &&
#else
(dp->d_ttl < (curtime+900)) &&
#endif
!(dp->d_flags & DB_F_HINT) )
{
dprintf(3, (ddt,
"nslookup: stale entry '%s'\n",
NAME(*np)));
/* Cache invalidate the NS RR's */
#ifndef DATUMREFCNT
if (dp->d_ttl < curtime)
#endif
{
delete_all(np, class, T_A);
n = oldn;
found_arr = 0;
goto need_sysquery;
}
}
#ifdef VALIDATE
/* anant@isi.edu validation procedure, maintains a
* table of server names-addresses used recently
*/
store_name_addr(dname, data_inaddr(dp->d_data),
syslogdname, sysloginfo);
#endif /*VALIDATE*/
found_arr++;
nsa = data_inaddr(dp->d_data);
/* don't put in duplicates */
qs = qp->q_addr;
for (i = 0; i < n; i++, qs++)
if (qs->ns_addr.sin_addr.s_addr == nsa.s_addr)
goto skipaddr;
qs->ns_addr.sin_family = AF_INET;
qs->ns_addr.sin_port = ns_port;
qs->ns_addr.sin_addr = nsa;
qs->ns = nsdp;
qs->nsdata = dp;
qs->nretry = 0;
/*
* if we are being asked to fwd a query whose
* nameserver list includes our own name/address(es),
* then we have detected a lame delegation and rather
* than melt down the network and hose down the other
* servers (who will hose us in return), we'll return
* -1 here which will cause SERVFAIL to be sent to
* the client's resolver which will hopefully then
* shut up.
*
* (originally done in nsContainsUs by vix@dec mar92;
* moved into nslookup by apb@und jan1993)
*
* try to limp along instead of denying service
* gdonl mar96
*/
if (aIsUs(nsa)) {
static char *complaint = "contains our address";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
/*
* If we want to forward to a host that asked us
* this question then either we or they are sick
* (unless they asked from some port other than
* their nameserver port). (apb@und jan1993)
*
* try to limp along instead of denying service
* gdonl mar96
*/
if (bcmp((char *)&qp->q_from, (char *)&qs->ns_addr,
sizeof(qp->q_from)) == 0)
{
static char *complaint = "forwarding loop";
nslookupComplain(sysloginfo, syslogdname,
complaint, dname, dp, nsdp);
continue;
}
#ifdef BOGUSNS
/*
* Don't forward queries to bogus servers. Note
* that this is unlike the previous tests, which
* are fatal to the query. Here we just skip the
* server, which is only fatal if it's the last
* server. Note also that we antialias here -- all
* A RR's of a server are considered the same server,
* and if any of them is bogus we skip the whole
* server. Those of you using multiple A RR's to
* load-balance your servers will (rightfully) lose
* here. But (unfortunately) only if they are bogus.
*/
if (addr_on_netlist(nsa, boglist))
goto skipserver;
#endif
n++;
if (n >= NSMAX)
goto out;
skipaddr:
NULL;
}
dprintf(8, (ddt, "nslookup: %d ns addrs\n", n));
need_sysquery:
if (found_arr == 0) {
potential_ns++;
if (!(qp->q_flags & Q_SYSTEM))
(void) sysquery(dname, class, T_A, NULL, 0,
QUERY);
}
skipserver:
NULL;
}
out:
dprintf(3, (ddt, "nslookup: %d ns addrs total\n", n));
qp->q_naddr = n;
if (n == 0 && potential_ns == 0 && !fwdtab) {
static char *complaint = "No possible A RRs";
if (sysloginfo && syslogdname &&
!haveComplained(syslogdname, complaint))
{
syslog(LOG_INFO, "%s: query(%s) %s",
sysloginfo, syslogdname, complaint);
}
return(-1);
}
#ifdef DATUMREFCNT
/* must be run before the sort */
for (i = naddr ; i < n ; i++) {
qp->q_addr[i].nsdata->d_rcnt++;
qp->q_addr[i].ns->d_rcnt++;
}
#endif
if (n > 1) {
qsort((char *)qp->q_addr, n, sizeof(struct qserv),
(int (*)__P((const void *, const void *)))qcomp);
}
return (n - naddr);
}
/*
* qcomp - compare two NS addresses, and return a negative, zero, or
* positive value depending on whether the first NS address is
* "better than", "equally good as", or "inferior to" the second
* NS address.
*
* How "goodness" is defined (for the purposes of this routine):
* - If the estimated round trip times differ by an amount deemed significant
* then the one with the smaller estimate is preferred; else
* - If we can determine which one is topologically closer then the
* closer one is preferred; else
* - The one with the smaller estimated round trip time is preferred
* (zero is returned if the two estimates are identical).
*
* How "topological closeness" is defined (for the purposes of this routine):
* Ideally, named could consult some magic map of the Internet and
* determine the length of the path to an arbitrary destination. Sadly,
* no such magic map exists. However, named does have a little bit of
* topological information in the form of the sortlist (which includes
* the directly connected subnet(s), the directly connected net(s), and
* any additional nets that the administrator has added using the "sortlist"
* directive in the bootfile. Thus, if only one of the addresses matches
* something in the sortlist then it is considered to be topologically
* closer. If both match, but match different entries in the sortlist,
* then the one that matches the entry closer to the beginning of the
* sorlist is considered to be topologically closer. In all other cases,
* topological closeness is ignored because it's either indeterminate or
* equal.
*
* How times are compared:
* Both times are rounded to the closest multiple of the NOISE constant
* defined below and then compared. If the rounded values are equal
* then the difference in the times is deemed insignificant. Rounding
* is used instead of merely taking the absolute value of the difference
* because doing the latter would make the ordering defined by this
* routine be incomplete in the mathematical sense (e.g. A > B and
* B > C would not imply A > C). The mathematics are important in
* practice to avoid core dumps in qsort().
*
* XXX: this doesn't solve the European root nameserver problem very well.
* XXX: we should detect and mark as inferior nameservers that give bogus
* answers
*
* (this was originally vixie's stuff but almquist fixed fatal bugs in it
* and wrote the above documentation)
*/
/*
* RTT delta deemed to be significant, in milliseconds. With the current
* definition of RTTROUND it must be a power of 2.
*/
#define NOISE 128 /* milliseconds; 0.128 seconds */
#define sign(x) (((x) < 0) ? -1 : ((x) > 0) ? 1 : 0)
#define RTTROUND(rtt) (((rtt) + (NOISE >> 1)) & ~(NOISE - 1))
int
qcomp(qs1, qs2)
struct qserv *qs1, *qs2;
{
int pos1, pos2, pdiff;
u_long rtt1, rtt2;
long tdiff;
if ((!qs1->nsdata) || (!qs2->nsdata))
return 0;
rtt1 = qs1->nsdata->d_nstime;
rtt2 = qs2->nsdata->d_nstime;
dprintf(10, (ddt, "qcomp(%s, %s) %lu (%lu) - %lu (%lu) = %lu",
inet_ntoa(qs1->ns_addr.sin_addr),
inet_ntoa(qs2->ns_addr.sin_addr),
rtt1, RTTROUND(rtt1), rtt2, RTTROUND(rtt2),
rtt1 - rtt2));
if (RTTROUND(rtt1) == RTTROUND(rtt2)) {
pos1 = position_on_netlist(qs1->ns_addr.sin_addr, nettab);
pos2 = position_on_netlist(qs2->ns_addr.sin_addr, nettab);
pdiff = pos1 - pos2;
dprintf(10, (ddt, ", pos1=%d, pos2=%d\n", pos1, pos2));
if (pdiff)
return (pdiff);
} else {
dprintf(10, (ddt, "\n"));
}
tdiff = rtt1 - rtt2;
return (sign(tdiff));
}
#undef sign
#undef RTTROUND
/*
* Arrange that forwarded query (qp) is retried after t seconds.
* Query list will be sorted after z_time is updated.
*/
void
schedretry(qp, t)
struct qinfo *qp;
time_t t;
{
register struct qinfo *qp1, *qp2;
#ifdef DEBUG
if (debug > 3) {
fprintf(ddt, "schedretry(0x%lx, %ld sec)\n",
(u_long)qp, (long)t);
if (qp->q_time)
fprintf(ddt,
"WARNING: schedretry(%#lx, %ld) q_time already %ld\n",
(u_long)qp, (long)t, (long)qp->q_time);
}
#endif
t += (u_long) tt.tv_sec;
qp->q_time = t;
if ((qp1 = retryqp) == NULL) {
retryqp = qp;
qp->q_next = NULL;
return;
}
if (t < qp1->q_time) {
qp->q_next = qp1;
retryqp = qp;
return;
}
while ((qp2 = qp1->q_next) != NULL && qp2->q_time < t)
qp1 = qp2;
qp1->q_next = qp;
qp->q_next = qp2;
}
/*
* Unsched is called to remove a forwarded query entry.
*/
void
unsched(qp)
struct qinfo *qp;
{
register struct qinfo *np;
dprintf(3, (ddt, "unsched(%#lx, %d)\n", (u_long)qp, ntohs(qp->q_id)));
if (retryqp == qp) {
retryqp = qp->q_next;
} else {
for (np=retryqp; np->q_next != QINFO_NULL; np = np->q_next) {
if (np->q_next != qp)
continue;
np->q_next = qp->q_next; /* dequeue */
break;
}
}
qp->q_next = QINFO_NULL; /* sanity check */
qp->q_time = 0;
}
/*
* Retry is called to retransmit query 'qp'.
*/
void
retry(qp)
register struct qinfo *qp;
{
register int n;
register HEADER *hp;
struct sockaddr_in *nsa;
dprintf(3, (ddt, "retry(x%lx) id=%d\n", (u_long)qp, ntohs(qp->q_id)));
if (qp->q_msg == NULL) { /* XXX - why? */
qremove(qp);
return;
}
if (qp->q_expire && (qp->q_expire < tt.tv_sec)) {
dprintf(1, (ddt,
"retry(x%lx): expired @ %lu (%d secs before now (%lu))\n",
(u_long)qp, (u_long)qp->q_expire,
(int)(tt.tv_sec - qp->q_expire),
(u_long)tt.tv_sec));
if (qp->q_stream) /* return failure code on stream */
goto fail;
qremove(qp);
return;
}
/* try next address */
n = qp->q_curaddr;
if (qp->q_fwd) {
qp->q_fwd = qp->q_fwd->next;
if (qp->q_fwd)
goto found;
/* out of forwarders, try direct queries */
} else
++qp->q_addr[n].nretry;
if (!forward_only) {
do {
if (++n >= (int)qp->q_naddr)
n = 0;
if (qp->q_addr[n].nretry < MAXRETRY)
goto found;
} while (n != qp->q_curaddr);
}
fail:
/*
* Give up. Can't reach destination.
*/
hp = (HEADER *)(qp->q_cmsg ? qp->q_cmsg : qp->q_msg);
if (qp->q_flags & Q_PRIMING) {
/* Can't give up priming */
unsched(qp);
schedretry(qp, (time_t)60*60); /* 1 hour */
hp->rcode = NOERROR; /* Lets be safe, reset the query */
hp->qr = hp->aa = 0;
qp->q_fwd = fwdtab;
for (n = 0; n < (int)qp->q_naddr; n++)
qp->q_addr[n].nretry = 0;
return;
}
dprintf(5, (ddt, "give up\n"));
n = ((HEADER *)qp->q_cmsg ? qp->q_cmsglen : qp->q_msglen);
hp->id = qp->q_id;
hp->qr = 1;
hp->ra = (NoRecurse == 0);
hp->rd = 1;
hp->rcode = SERVFAIL;
#ifdef DEBUG
if (debug >= 10)
fp_nquery(qp->q_msg, n, ddt);
#endif
if (send_msg((u_char *)hp, n, qp)) {
dprintf(1, (ddt, "gave up retry(x%lx) nsid=%d id=%d\n",
(u_long)qp, ntohs(qp->q_nsid), ntohs(qp->q_id)));
}
#ifdef XSTATS
nameserIncr(qp->q_from.sin_addr, nssSentFail);
#endif
qremove(qp);
return;
found:
if (qp->q_fwd == 0 && qp->q_addr[n].nretry == 0)
qp->q_addr[n].stime = tt;
qp->q_curaddr = n;
hp = (HEADER *)qp->q_msg;
hp->rd = (qp->q_fwd ? 1 : 0);
nsa = Q_NEXTADDR(qp, n);
dprintf(1, (ddt,
"%s(addr=%d n=%d) -> [%s].%d ds=%d nsid=%d id=%d %dms\n",
(qp->q_fwd ? "reforw" : "resend"),
n, qp->q_addr[n].nretry,
inet_ntoa(nsa->sin_addr),
ntohs(nsa->sin_port), ds,
ntohs(qp->q_nsid), ntohs(qp->q_id),
(qp->q_addr[n].nsdata != 0)
? qp->q_addr[n].nsdata->d_nstime
: (-1)));
#ifdef DEBUG
if (debug >= 10)
fp_nquery(qp->q_msg, qp->q_msglen, ddt);
#endif
/* NOSTRICT */
if (sendto(ds, (char*)qp->q_msg, qp->q_msglen, 0,
(struct sockaddr *)nsa,
sizeof(struct sockaddr_in)) < 0) {
dprintf(3, (ddt, "error resending msg errno=%d\n", errno));
}
hp->rd = 1; /* leave set to 1 for dup detection */
nameserIncr(nsa->sin_addr, nssSentDupQ);
unsched(qp);
#ifdef SLAVE_FORWARD
if(forward_only)
schedretry(qp, (time_t)slave_retry);
else
#endif /* SLAVE_FORWARD */
schedretry(qp, qp->q_fwd ? (2*RETRYBASE) : retrytime(qp));
}
/*
* Compute retry time for the next server for a query.
* Use a minimum time of RETRYBASE (4 sec.) or twice the estimated
* service time; * back off exponentially on retries, but place a 45-sec.
* ceiling on retry times for now. (This is because we don't hold a reference
* on servers or their addresses, and we have to finish before they time out.)
*/
time_t
retrytime(qp)
struct qinfo *qp;
{
time_t t, u, v;
struct qserv *ns = &qp->q_addr[qp->q_curaddr];
if (ns->nsdata != NULL)
t = (time_t) MAX(RETRYBASE, 2 * ns->nsdata->d_nstime / 1000);
else
t = (time_t) RETRYBASE;
u = t << ns->nretry;
v = MIN(u, RETRY_TIMEOUT); /* max. retry timeout for now */
dprintf(3, (ddt, "retrytime: nstime%ldms t%ld nretry%ld u%ld : v%ld\n",
ns->nsdata ?(long)(ns->nsdata->d_nstime / 1000) :(long)-1,
(long)t, (long)ns->nretry, (long)u, (long)v));
return (v);
}
void
qflush()
{
while (nsqhead)
qremove(nsqhead);
nsqhead = QINFO_NULL;
}
void
qremove(qp)
register struct qinfo *qp;
{
dprintf(3, (ddt, "qremove(x%lx)\n", (u_long)qp));
if (qp->q_flags & Q_ZSERIAL)
qserial_answer(qp, 0);
unsched(qp);
qfree(qp);
}
#if defined(__STDC__) || defined(__GNUC__)
struct qinfo *
qfindid(u_int16_t id)
#else
struct qinfo *
qfindid(id)
register u_int16_t id;
#endif
{
register struct qinfo *qp;
dprintf(3, (ddt, "qfindid(%d)\n", ntohs(id)));
for (qp = nsqhead; qp!=QINFO_NULL; qp = qp->q_link) {
if (qp->q_nsid == id)
return(qp);
}
dprintf(5, (ddt, "qp not found\n"));
return (NULL);
}
struct qinfo *
#ifdef DMALLOC
qnew_tagged(file, line)
char *file;
int line;
#else
qnew()
#endif
{
register struct qinfo *qp;
qp = (struct qinfo *)
#ifdef DMALLOC
dcalloc(file, line, 1, sizeof(struct qinfo));
#else
calloc(1, sizeof(struct qinfo));
#endif
if (qp == NULL) {
dprintf(5, (ddt, "qnew: calloc error\n"));
syslog(LOG_ERR, "forw: %m");
exit(12);
}
dprintf(5, (ddt, "qnew(x%lx)\n", (u_long)qp));
#ifdef BIND_NOTIFY
qp->q_notifyzone = DB_Z_CACHE;
#endif
qp->q_link = nsqhead;
nsqhead = qp;
return (qp);
}
void
qfree(qp)
struct qinfo *qp;
{
register struct qinfo *np;
register struct databuf *dp;
#ifdef DATUMREFCNT
int i;
#endif
dprintf(3, (ddt, "Qfree(x%lx)\n", (u_long)qp));
if (qp->q_next)
dprintf(1, (ddt, "WARNING: qfree of linked ptr x%lx\n",
(u_long)qp));
if (qp->q_msg)
free(qp->q_msg);
if (qp->q_cmsg)
free(qp->q_cmsg);
#ifdef DATUMREFCNT
for (i = 0 ; i < (int)qp->q_naddr ; i++) {
dp = qp->q_addr[i].ns;
if (dp)
if (--(dp->d_rcnt)) {
dprintf(3, (ddt, "qfree: ns %s rcnt %d\n",
dp->d_data,
dp->d_rcnt));
} else {
dprintf(3, (ddt, "qfree: ns %s rcnt %d delayed\n",
dp->d_data,
dp->d_rcnt));
free((char*)dp);
}
dp = qp->q_addr[i].nsdata;
if (dp)
if ((--(dp->d_rcnt))) {
dprintf(3, (ddt, "qfree: nsdata %08.8X rcnt %d\n",
*(int32_t *)(dp->d_data),
dp->d_rcnt));
} else {
dprintf(3, (ddt, "qfree: nsdata %08.8X rcnt %d delayed\n",
*(int32_t *)(dp->d_data),
dp->d_rcnt));
free((char*)dp);
}
}
#endif
if( nsqhead == qp ) {
nsqhead = qp->q_link;
} else {
for( np=nsqhead; np->q_link != QINFO_NULL; np = np->q_link ) {
if( np->q_link != qp ) continue;
np->q_link = qp->q_link; /* dequeue */
break;
}
}
free((char *)qp);
}