]> jfr.im git - solanum.git/blobdiff - src/res.c
Merge pull request #53 from ShadowNinja/clarify_U+R
[solanum.git] / src / res.c
index e84e7a58f706a12342600372a75548665f294201..301ff722748d527b86bdcdeded861ef5f46979dd 100644 (file)
--- a/src/res.c
+++ b/src/res.c
@@ -15,7 +15,7 @@
  *     --Bleep (Thomas Helvey <tomh@inxpress.net>)
  *
  * This was all needlessly complicated for irc. Simplified. No more hostent
- * All we really care about is the IP -> hostname mappings. Thats all. 
+ * All we really care about is the IP -> hostname mappings. Thats all.
  *
  * Apr 28, 2003 --cryogen and Dianora
  *
  * removed, various robustness fixes
  *
  * 2006 --jilles and nenolod
+ *
+ * Resend queries to other servers if the DNS server replies with an error or
+ * an invalid response. Also, avoid servers that return errors or invalid
+ * responses.
+ *
+ * October 2012 --mr_flea
  */
 
 #include "stdinc.h"
 #include "ircd.h"
 #include "res.h"
 #include "reslib.h"
-#include "irc_string.h"
-#include "sprintf_irc.h"
+#include "match.h"
 #include "numeric.h"
 #include "client.h" /* SNO_* */
+#include "s_assert.h"
+#include "logger.h"
+#include "send.h"
 
 #if (CHAR_BIT != 8)
 #error this code needs to be able to address individual octets
@@ -57,35 +65,26 @@ static PF res_readreply;
 #define RDLENGTH_SIZE     (size_t)2
 #define ANSWER_FIXED_SIZE (TYPE_SIZE + CLASS_SIZE + TTL_SIZE + RDLENGTH_SIZE)
 
-typedef enum
-{
-       REQ_IDLE,               /* We're doing not much at all */
-       REQ_PTR,                /* Looking up a PTR */
-       REQ_A,                  /* Looking up an A or AAAA */
-       REQ_CNAME               /* We got a CNAME in response, we better get a real answer next */
-} request_state;
-
 struct reslist
 {
        rb_dlink_node node;
        int id;
-       int sent;               /* number of requests sent */
-       request_state state;    /* State the resolver machine is in */
        time_t ttl;
        char type;
-       char queryname[128];    /* name currently being queried */
+       char queryname[IRCD_RES_HOSTLEN + 1]; /* name currently being queried */
        char retries;           /* retry counter */
        char sends;             /* number of sends (>1 means resent) */
-       char resend;            /* send flag. 0 == dont resend */
        time_t sentat;
        time_t timeout;
+       unsigned int lastns;    /* index of last server sent to */
        struct rb_sockaddr_storage addr;
        char *name;
        struct DNSQuery *query; /* query callback for this request */
 };
 
-static int res_fd;
+static rb_fde_t *res_fd;
 static rb_dlink_list request_list = { NULL, NULL, 0 };
+static int ns_failure_count[IRCD_MAXNS]; /* timeouts and invalid/failed replies */
 
 static void rem_request(struct reslist *request);
 static struct reslist *make_request(struct DNSQuery *query);
@@ -100,73 +99,74 @@ static int proc_answer(struct reslist *request, HEADER * header, char *, char *)
 static struct reslist *find_id(int id);
 static struct DNSReply *make_dnsreply(struct reslist *request);
 
-extern struct rb_sockaddr_storage irc_nsaddr_list[IRCD_MAXNS];
-extern int irc_nscount;
-extern char irc_domain[HOSTLEN + 1];
-
-
 /*
  * int
  * res_ourserver(inp)
  *      looks up "inp" in irc_nsaddr_list[]
  * returns:
- *      0  : not found
- *      >0 : found
+ *      server ID or -1 for not found
  * author:
  *      paul vixie, 29may94
  *      revised for ircd, cryogen(stu) may03
+ *      slightly modified for charybdis, mr_flea oct12
  */
 static int res_ourserver(const struct rb_sockaddr_storage *inp)
 {
-#ifdef IPV6
-       struct sockaddr_in6 *v6;
-       struct sockaddr_in6 *v6in = (struct sockaddr_in6 *)inp;
+#ifdef RB_IPV6
+       const struct sockaddr_in6 *v6;
+       const struct sockaddr_in6 *v6in = (const struct sockaddr_in6 *)inp;
 #endif
-       struct sockaddr_in *v4;
-       struct sockaddr_in *v4in = (struct sockaddr_in *)inp;
+       const struct sockaddr_in *v4;
+       const struct sockaddr_in *v4in = (const struct sockaddr_in *)inp;
        int ns;
 
        for (ns = 0; ns < irc_nscount; ns++)
        {
                const struct rb_sockaddr_storage *srv = &irc_nsaddr_list[ns];
-#ifdef IPV6
-               v6 = (struct sockaddr_in6 *)srv;
+
+               if (srv->ss_family != inp->ss_family)
+                       continue;
+
+#ifdef RB_IPV6
+               v6 = (const struct sockaddr_in6 *)srv;
 #endif
-               v4 = (struct sockaddr_in *)srv;
+               v4 = (const struct sockaddr_in *)srv;
 
                /* could probably just memcmp(srv, inp, srv.ss_len) here
-                * but we'll air on the side of caution - stu
+                * but we'll err on the side of caution - stu
                 */
                switch (srv->ss_family)
                {
-#ifdef IPV6
-                 case AF_INET6:
-                         if (srv->ss_family == inp->ss_family)
-                                 if (v6->sin6_port == v6in->sin6_port)
-                                         if ((memcmp(&v6->sin6_addr.s6_addr, &v6in->sin6_addr.s6_addr,
-                                               sizeof(struct in6_addr)) == 0) ||
-                                             (memcmp(&v6->sin6_addr.s6_addr, &in6addr_any,
-                                               sizeof(struct in6_addr)) == 0))
-                                                 return 1;
-                         break;
+#ifdef RB_IPV6
+                       case AF_INET6:
+                               if (v6->sin6_port == v6in->sin6_port)
+                                       if ((memcmp(&v6->sin6_addr.s6_addr, &v6in->sin6_addr.s6_addr,
+                                                                       sizeof(struct in6_addr)) == 0) ||
+                                                       (memcmp(&v6->sin6_addr.s6_addr, &in6addr_any,
+                                                                       sizeof(struct in6_addr)) == 0))
+                                       {
+                                               return ns;
+                                       }
+                               break;
 #endif
-                 case AF_INET:
-                         if (srv->ss_family == inp->ss_family)
-                                 if (v4->sin_port == v4in->sin_port)
-                                         if ((v4->sin_addr.s_addr == INADDR_ANY)
-                                             || (v4->sin_addr.s_addr == v4in->sin_addr.s_addr))
-                                                 return 1;
-                         break;
-                 default:
-                         break;
+                       case AF_INET:
+                               if (v4->sin_port == v4in->sin_port)
+                                       if ((v4->sin_addr.s_addr == INADDR_ANY)
+                                                       || (v4->sin_addr.s_addr == v4in->sin_addr.s_addr))
+                                       {
+                                               return ns;
+                                       }
+                               break;
+                       default:
+                               break;
                }
        }
 
-       return 0;
+       return -1;
 }
 
 /*
- * timeout_query_list - Remove queries from the list which have been 
+ * timeout_query_list - Remove queries from the list which have been
  * there too long without being resolved.
  */
 static time_t timeout_query_list(time_t now)
@@ -184,18 +184,10 @@ static time_t timeout_query_list(time_t now)
 
                if (now >= timeout)
                {
-                       if (--request->retries <= 0)
-                       {
-                               (*request->query->callback) (request->query->ptr, NULL);
-                               rem_request(request);
-                               continue;
-                       }
-                       else
-                       {
-                               request->sentat = now;
-                               request->timeout += request->timeout;
-                               resend_query(request);
-                       }
+                       ns_failure_count[request->lastns]++;
+                       request->sentat = now;
+                       request->timeout += request->timeout;
+                       resend_query(request);
                }
 
                if ((next_time == 0) || timeout < next_time)
@@ -215,23 +207,29 @@ static void timeout_resolver(void *notused)
        timeout_query_list(rb_current_time());
 }
 
+static struct ev_entry *timeout_resolver_ev = NULL;
+
 /*
  * start_resolver - do everything we need to read the resolv.conf file
  * and initialize the resolver file descriptor if needed
  */
 static void start_resolver(void)
 {
+       int i;
+
        irc_res_init();
+       for (i = 0; i < irc_nscount; i++)
+               ns_failure_count[i] = 0;
 
-       if (res_fd <= 0)        /* there isn't any such thing as fd 0, that's just a myth. */
+       if (res_fd == NULL)
        {
                if ((res_fd = rb_socket(irc_nsaddr_list[0].ss_family, SOCK_DGRAM, 0,
-                              "UDP resolver socket")) == -1)
+                              "UDP resolver socket")) == NULL)
                        return;
 
                /* At the moment, the resolver FD data is global .. */
-               rb_setselect(res_fd, FDLIST_NONE, COMM_SELECT_READ, res_readreply, NULL, 0);
-               rb_event_add("timeout_resolver", timeout_resolver, NULL, 1);
+               rb_setselect(res_fd, RB_SELECT_READ, res_readreply, NULL);
+               timeout_resolver_ev = rb_event_add("timeout_resolver", timeout_resolver, NULL, 1);
        }
 }
 
@@ -252,8 +250,8 @@ void init_resolver(void)
 void restart_resolver(void)
 {
        rb_close(res_fd);
-       res_fd = -1;
-       eventDelete(timeout_resolver, NULL);    /* -ddosen */
+       res_fd = NULL;
+       rb_event_delete(timeout_resolver_ev);   /* -ddosen */
        start_resolver();
 }
 
@@ -280,8 +278,8 @@ void add_local_domain(char *hname, size_t size)
 }
 
 /*
- * rem_request - remove a request from the list. 
- * This must also free any memory that has been allocated for 
+ * rem_request - remove a request from the list.
+ * This must also free any memory that has been allocated for
  * temporary storage of DNS results.
  */
 static void rem_request(struct reslist *request)
@@ -300,10 +298,33 @@ static struct reslist *make_request(struct DNSQuery *query)
 
        request->sentat = rb_current_time();
        request->retries = 3;
-       request->resend = 1;
        request->timeout = 4;   /* start at 4 and exponential inc. */
        request->query = query;
-       request->state = REQ_IDLE;
+
+       /*
+        * generate a unique id
+        * NOTE: we don't have to worry about converting this to and from
+        * network byte order, the nameserver does not interpret this value
+        * and returns it unchanged
+        *
+        * we generate an id per request now (instead of per send) to allow
+        * late replies to be used.
+        */
+#ifdef HAVE_LRAND48
+       do
+       {
+               request->id = (request->id + lrand48()) & 0xffff;
+       } while (find_id(request->id));
+#else
+       int k = 0;
+       struct timeval tv;
+       gettimeofday(&tv, NULL);
+       do
+       {
+               request->id = (request->id + k + tv.tv_usec) & 0xffff;
+               k++;
+       } while (find_id(request->id));
+#endif /* HAVE_LRAND48 */
 
        rb_dlinkAdd(request, &request->node, &request_list);
 
@@ -311,7 +332,7 @@ static struct reslist *make_request(struct DNSQuery *query)
 }
 
 /*
- * delete_resolver_queries - cleanup outstanding queries 
+ * delete_resolver_queries - cleanup outstanding queries
  * for which there no longer exist clients or conf lines.
  */
 void delete_resolver_queries(const struct DNSQuery *query)
@@ -331,33 +352,67 @@ void delete_resolver_queries(const struct DNSQuery *query)
 }
 
 /*
- * send_res_msg - sends msg to all nameservers found in the "_res" structure.
- * This should reflect /etc/resolv.conf. We will get responses
- * which arent needed but is easier than checking to see if nameserver
- * isnt present. Returns number of messages successfully sent to 
- * nameservers or -1 if no successful sends.
+ * retryfreq - determine how many queries to wait before resending
+ * if there have been that many consecutive timeouts
+ */
+static int retryfreq(int timeouts)
+{
+       switch (timeouts)
+       {
+               case 1:
+                       return 3;
+               case 2:
+                       return 9;
+               case 3:
+                       return 27;
+               case 4:
+                       return 81;
+               default:
+                       return 243;
+       }
+}
+
+/*
+ * send_res_msg - sends msg to a nameserver.
+ * This should reflect /etc/resolv.conf.
+ * Returns number of nameserver successfully sent to
+ * or -1 if no successful sends.
  */
 static int send_res_msg(const char *msg, int len, int rcount)
 {
        int i;
-       int sent = 0;
-       int max_queries = IRCD_MIN(irc_nscount, rcount);
+       int ns;
+       static int retrycnt;
 
-       /* RES_PRIMARY option is not implemented
-        * if (res.options & RES_PRIMARY || 0 == max_queries)
+       retrycnt++;
+       /* First try a nameserver that seems to work.
+        * Every once in a while, try a possibly broken one to check
+        * if it is working again.
         */
-       if (max_queries == 0)
-               max_queries = 1;
+       for (i = 0; i < irc_nscount; i++)
+       {
+               ns = (i + rcount - 1) % irc_nscount;
+               if (ns_failure_count[ns] && retrycnt % retryfreq(ns_failure_count[ns]))
+                       continue;
+               if (sendto(rb_get_fd(res_fd), msg, len, 0,
+                    (struct sockaddr *)&(irc_nsaddr_list[ns]),
+                               GET_SS_LEN(&irc_nsaddr_list[ns])) == len)
+                       return ns;
+       }
 
-       for (i = 0; sent < max_queries && i < irc_nscount; i++)
+       /* No known working nameservers, try some broken one. */
+       for (i = 0; i < irc_nscount; i++)
        {
-               if (sendto(res_fd, msg, len, 0,
-                    (struct sockaddr *)&(irc_nsaddr_list[i]), 
-                               GET_SS_LEN(irc_nsaddr_list[i])) == len)
-                       ++sent;
+               ns = (i + rcount - 1) % irc_nscount;
+               if (!ns_failure_count[ns])
+                       continue;
+               if (sendto(rb_get_fd(res_fd), msg, len, 0,
+                    (struct sockaddr *)&(irc_nsaddr_list[ns]),
+                               GET_SS_LEN(&irc_nsaddr_list[ns])) == len)
+                       return ns;
        }
 
-       return (sent);
+       return -1;
 }
 
 /*
@@ -379,7 +434,7 @@ static struct reslist *find_id(int id)
        return (NULL);
 }
 
-/* 
+/*
  * gethost_byname_type - get host address from name
  *
  */
@@ -403,20 +458,19 @@ void gethost_byaddr(const struct rb_sockaddr_storage *addr, struct DNSQuery *que
 static void do_query_name(struct DNSQuery *query, const char *name, struct reslist *request,
                          int type)
 {
-       char host_name[HOSTLEN + 1];
+       char host_name[IRCD_RES_HOSTLEN + 1];
 
-       strlcpy(host_name, name, HOSTLEN + 1);
-       add_local_domain(host_name, HOSTLEN);
+       rb_strlcpy(host_name, name, IRCD_RES_HOSTLEN + 1);
+       add_local_domain(host_name, IRCD_RES_HOSTLEN);
 
        if (request == NULL)
        {
                request = make_request(query);
                request->name = (char *)rb_malloc(strlen(host_name) + 1);
                strcpy(request->name, host_name);
-               request->state = REQ_A;
        }
 
-       strlcpy(request->queryname, host_name, sizeof(request->queryname));
+       rb_strlcpy(request->queryname, host_name, sizeof(request->queryname));
        request->type = type;
        query_name(request);
 }
@@ -433,21 +487,21 @@ static void do_query_number(struct DNSQuery *query, const struct rb_sockaddr_sto
        {
                request = make_request(query);
                memcpy(&request->addr, addr, sizeof(struct rb_sockaddr_storage));
-               request->name = (char *)rb_malloc(HOSTLEN + 1);
+               request->name = (char *)rb_malloc(IRCD_RES_HOSTLEN + 1);
        }
 
        if (addr->ss_family == AF_INET)
        {
-               struct sockaddr_in *v4 = (struct sockaddr_in *)addr;
+               const struct sockaddr_in *v4 = (const struct sockaddr_in *)addr;
                cp = (const unsigned char *)&v4->sin_addr.s_addr;
 
                rb_sprintf(request->queryname, "%u.%u.%u.%u.in-addr.arpa", (unsigned int)(cp[3]),
                           (unsigned int)(cp[2]), (unsigned int)(cp[1]), (unsigned int)(cp[0]));
        }
-#ifdef IPV6
+#ifdef RB_IPV6
        else if (addr->ss_family == AF_INET6)
        {
-               struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)addr;
+               const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)addr;
                cp = (const unsigned char *)&v6->sin6_addr.s6_addr;
 
                (void)sprintf(request->queryname, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
@@ -482,6 +536,7 @@ static void query_name(struct reslist *request)
 {
        char buf[MAXPACKET];
        int request_len = 0;
+       int ns;
 
        memset(buf, 0, sizeof(buf));
 
@@ -489,40 +544,23 @@ static void query_name(struct reslist *request)
             irc_res_mkquery(request->queryname, C_IN, request->type, (unsigned char *)buf, sizeof(buf))) > 0)
        {
                HEADER *header = (HEADER *) buf;
-#ifndef HAVE_LRAND48
-               int k = 0;
-               struct timeval tv;
-#endif
-               /*
-                * generate an unique id
-                * NOTE: we don't have to worry about converting this to and from
-                * network byte order, the nameserver does not interpret this value
-                * and returns it unchanged
-                */
-#ifdef HAVE_LRAND48
-               do
-               {
-                       header->id = (header->id + lrand48()) & 0xffff;
-               } while (find_id(header->id));
-#else
-               gettimeofday(&tv, NULL);
-               do
-               {
-                       header->id = (header->id + k + tv.tv_usec) & 0xffff;
-                       k++;
-               } while (find_id(header->id));
-#endif /* HAVE_LRAND48 */
-               request->id = header->id;
+               header->id = request->id;
                ++request->sends;
 
-               request->sent += send_res_msg(buf, request_len, request->sends);
+               ns = send_res_msg(buf, request_len, request->sends);
+               if (ns != -1)
+                       request->lastns = ns;
        }
 }
 
 static void resend_query(struct reslist *request)
 {
-       if (request->resend == 0)
+       if (--request->retries <= 0)
+       {
+               (*request->query->callback) (request->query->ptr, NULL);
+               rem_request(request);
                return;
+       }
 
        switch (request->type)
        {
@@ -530,7 +568,7 @@ static void resend_query(struct reslist *request)
                  do_query_number(NULL, &request->addr, request);
                  break;
          case T_A:
-#ifdef IPV6
+#ifdef RB_IPV6
          case T_AAAA:
 #endif
                  do_query_name(NULL, request->name, request, request->type);
@@ -547,7 +585,7 @@ static void resend_query(struct reslist *request)
  */
 static int check_question(struct reslist *request, HEADER * header, char *buf, char *eob)
 {
-       char hostbuf[128];      /* working buffer */
+       char hostbuf[IRCD_RES_HOSTLEN + 1];     /* working buffer */
        unsigned char *current; /* current position in buf */
        int n;                  /* temp count */
 
@@ -568,14 +606,14 @@ static int check_question(struct reslist *request, HEADER * header, char *buf, c
  */
 static int proc_answer(struct reslist *request, HEADER * header, char *buf, char *eob)
 {
-       char hostbuf[HOSTLEN + 100];    /* working buffer */
+       char hostbuf[IRCD_RES_HOSTLEN + 100];   /* working buffer */
        unsigned char *current; /* current position in buf */
        int query_class;        /* answer class */
        int type;               /* answer type */
        int n;                  /* temp count */
        int rd_length;
        struct sockaddr_in *v4; /* conversion */
-#ifdef IPV6
+#ifdef RB_IPV6
        struct sockaddr_in6 *v6;
 #endif
        current = (unsigned char *)buf + sizeof(HEADER);
@@ -613,7 +651,7 @@ static int proc_answer(struct reslist *request, HEADER * header, char *buf, char
                        return (0);
                }
 
-               hostbuf[HOSTLEN] = '\0';
+               hostbuf[IRCD_RES_HOSTLEN] = '\0';
 
                /* With Address arithmetic you have to be very anal
                 * this code was not working on alpha due to that
@@ -636,8 +674,8 @@ static int proc_answer(struct reslist *request, HEADER * header, char *buf, char
                rd_length = irc_ns_get16(current);
                current += RDLENGTH_SIZE;
 
-               /* 
-                * Wait to set request->type until we verify this structure 
+               /*
+                * Wait to set request->type until we verify this structure
                 */
                switch (type)
                {
@@ -651,18 +689,18 @@ static int proc_answer(struct reslist *request, HEADER * header, char *buf, char
                          if (rd_length != sizeof(struct in_addr))
                                  return (0);
                          v4 = (struct sockaddr_in *)&request->addr;
-                         SET_SS_LEN(request->addr, sizeof(struct sockaddr_in));
+                         SET_SS_LEN(&request->addr, sizeof(struct sockaddr_in));
                          v4->sin_family = AF_INET;
                          memcpy(&v4->sin_addr, current, sizeof(struct in_addr));
                          return (1);
                          break;
-#ifdef IPV6
+#ifdef RB_IPV6
                  case T_AAAA:
                          if (request->type != T_AAAA)
                                  return (0);
                          if (rd_length != sizeof(struct in6_addr))
                                  return (0);
-                         SET_SS_LEN(request->addr, sizeof(struct sockaddr_in6));
+                         SET_SS_LEN(&request->addr, sizeof(struct sockaddr_in6));
                          v6 = (struct sockaddr_in6 *)&request->addr;
                          v6->sin6_family = AF_INET6;
                          memcpy(&v6->sin6_addr, current, sizeof(struct in6_addr));
@@ -679,26 +717,12 @@ static int proc_answer(struct reslist *request, HEADER * header, char *buf, char
                          else if (n == 0)
                                  return (0);   /* no more answers left */
 
-                         strlcpy(request->name, hostbuf, HOSTLEN + 1);
+                         rb_strlcpy(request->name, hostbuf, IRCD_RES_HOSTLEN + 1);
 
                          return (1);
                          break;
-                 case T_CNAME: /* first check we already havent started looking 
-                                          into a cname */
-                         if (request->type != T_PTR)
-                                 return (0);
-
-                         if (request->state == REQ_CNAME)
-                         {
-                                 n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob,
-                                                   current, hostbuf, sizeof(hostbuf));
-
-                                 if (n < 0)
-                                         return (0);
-                                 return (1);
-                         }
-
-                         request->state = REQ_CNAME;
+                 case T_CNAME:
+                         /* real answer will follow */
                          current += rd_length;
                          break;
 
@@ -716,13 +740,14 @@ static int proc_answer(struct reslist *request, HEADER * header, char *buf, char
 }
 
 /*
- * res_readreply - read a dns reply from the nameserver and process it.
+ * res_read_single_reply - read a dns reply from the nameserver and process it.
+ * Return value: 1 if a packet was read, 0 otherwise
  */
-static void res_readreply(int fd, void *data)
+static int res_read_single_reply(rb_fde_t *F, void *data)
 {
        char buf[sizeof(HEADER) + MAXPACKET]
-               /* Sparc and alpha need 16bit-alignment for accessing header->id 
-                * (which is uint16_t). Because of the header = (HEADER*) buf; 
+               /* Sparc and alpha need 16bit-alignment for accessing header->id
+                * (which is uint16_t). Because of the header = (HEADER*) buf;
                 * lateron, this is neeeded. --FaUl
                 */
 #if defined(__sparc__) || defined(__alpha__)
@@ -736,16 +761,17 @@ static void res_readreply(int fd, void *data)
        int answer_count;
        socklen_t len = sizeof(struct rb_sockaddr_storage);
        struct rb_sockaddr_storage lsin;
+       int ns;
 
-       rc = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&lsin, &len);
+       rc = recvfrom(rb_get_fd(F), buf, sizeof(buf), 0, (struct sockaddr *)&lsin, &len);
 
-       /* Re-schedule a read *after* recvfrom, or we'll be registering
-        * interest where it'll instantly be ready for read :-) -- adrian
-        */
-       rb_setselect(fd, FDLIST_NONE, COMM_SELECT_READ, res_readreply, NULL, 0);
-       /* Better to cast the sizeof instead of rc */
+       /* No packet */
+       if (rc == 0 || rc == -1)
+               return 0;
+
+       /* Too small */
        if (rc <= (int)(sizeof(HEADER)))
-               return;
+               return 1;
 
        /*
         * convert DNS reply reader from Network byte order to CPU byte order.
@@ -761,38 +787,61 @@ static void res_readreply(int fd, void *data)
         * just ignore this response.
         */
        if (0 == (request = find_id(header->id)))
-               return;
+               return 1;
 
        /*
         * check against possibly fake replies
         */
-       if (!res_ourserver(&lsin))
-               return;
+       ns = res_ourserver(&lsin);
+       if (ns == -1)
+               return 1;
+
+       if (ns != request->lastns)
+       {
+               /*
+                * We'll accept the late reply, but penalize it a little more to make
+                * sure a laggy server doesn't end up favored.
+                */
+               ns_failure_count[ns] += 3;
+       }
+
 
        if (!check_question(request, header, buf, buf + rc))
-               return;
+               return 1;
 
        if ((header->rcode != NO_ERRORS) || (header->ancount == 0))
        {
-               if (NXDOMAIN == header->rcode)
+               /*
+                * RFC 2136 states that in the event of a server returning SERVFAIL
+                * or NOTIMP, the request should be resent to the next server.
+                * Additionally, if the server refuses our query, resend it as well.
+                * -- mr_flea
+                */
+               if (SERVFAIL == header->rcode || NOTIMP == header->rcode ||
+                               REFUSED == header->rcode)
                {
-                       (*request->query->callback) (request->query->ptr, NULL);
-                       rem_request(request);
+                       ns_failure_count[ns]++;
+                       resend_query(request);
                }
                else
                {
                        /*
-                        * If a bad error was returned, we stop here and dont send
-                        * send any more (no retries granted).
+                        * Either a fatal error was returned or no answer. Cancel the
+                        * request.
                         */
+                       if (NXDOMAIN == header->rcode)
+                       {
+                               /* If the rcode is NXDOMAIN, treat it as a good response. */
+                               ns_failure_count[ns] /= 4;
+                       }
                        (*request->query->callback) (request->query->ptr, NULL);
                        rem_request(request);
                }
-               return;
+               return 1;
        }
        /*
-        * If this fails there was an error decoding the received packet
-        * give up. -- jilles
+        * If this fails there was an error decoding the received packet.
+        * -- jilles
         */
        answer_count = proc_answer(request, header, buf, buf + rc);
 
@@ -803,20 +852,19 @@ static void res_readreply(int fd, void *data)
                        if (request->name == NULL)
                        {
                                /*
-                                * got a PTR response with no name, something bogus is happening
-                                * don't bother trying again, the client address doesn't resolve
+                                * Got a PTR response with no name, something strange is
+                                * happening. Try another DNS server.
                                 */
-                               (*request->query->callback) (request->query->ptr, reply);
-                               rem_request(request);
-                               return;
+                               ns_failure_count[ns]++;
+                               resend_query(request);
+                               return 1;
                        }
 
                        /*
                         * Lookup the 'authoritative' name that we were given for the
-                        * ip#. 
-                        *
+                        * ip#.
                         */
-#ifdef IPV6
+#ifdef RB_IPV6
                        if (request->addr.ss_family == AF_INET6)
                                gethost_byname_type(request->name, request->query, T_AAAA);
                        else
@@ -834,13 +882,23 @@ static void res_readreply(int fd, void *data)
                        rb_free(reply);
                        rem_request(request);
                }
+
+               ns_failure_count[ns] /= 4;
        }
        else
        {
-               /* couldn't decode, give up -- jilles */
-               (*request->query->callback) (request->query->ptr, NULL);
-               rem_request(request);
+               /* Invalid or corrupt reply - try another resolver. */
+               ns_failure_count[ns]++;
+               resend_query(request);
        }
+       return 1;
+}
+
+static void res_readreply(rb_fde_t *F, void *data)
+{
+       while (res_read_single_reply(F, data))
+               ;
+       rb_setselect(F, RB_SELECT_READ, res_readreply, NULL);
 }
 
 static struct DNSReply *make_dnsreply(struct reslist *request)
@@ -862,10 +920,10 @@ void report_dns_servers(struct Client *source_p)
 
        for (i = 0; i < irc_nscount; i++)
        {
-               if (!inetntop_sock((struct sockaddr *)&(irc_nsaddr_list[i]),
+               if (!rb_inet_ntop_sock((struct sockaddr *)&(irc_nsaddr_list[i]),
                                ipaddr, sizeof ipaddr))
-                       strlcpy(ipaddr, "?", sizeof ipaddr);
+                       rb_strlcpy(ipaddr, "?", sizeof ipaddr);
                sendto_one_numeric(source_p, RPL_STATSDEBUG,
-                               "A %s", ipaddr);
+                               "A %s %d", ipaddr, ns_failure_count[i]);
        }
 }