From 51c852cb13f9cbca3803b05372bc5a5f4b12bebb Mon Sep 17 00:00:00 2001
Message-Id: <51c852cb13f9cbca3803b05372bc5a5f4b12bebb.1499169412.git.fweimer@redhat.com>
From: Florian Weimer <fweimer@redhat.com>
Date: Tue, 4 Jul 2017 13:56:33 +0200
Subject: [PATCH] resolv: Add logging to resolv_conf processing
To: libc-alpha@sourceware.org

---
 resolv/resolv_conf.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 178 insertions(+), 14 deletions(-)

diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c
index 0ed36cd..375c072 100644
--- a/resolv/resolv_conf.c
+++ b/resolv/resolv_conf.c
@@ -19,9 +19,11 @@
 #include <resolv_conf.h>
 
 #include <alloc_buffer.h>
+#include <arpa/inet.h>
 #include <assert.h>
 #include <libc-lock.h>
 #include <resolv-internal.h>
+#include <stdio.h>
 #include <sys/stat.h>
 
 /* _res._u._ext.__glibc_extension_index is used as an index into a
@@ -257,9 +259,9 @@ same_address (const struct sockaddr *left, const struct sockaddr *right)
 }
 
 /* Check that *RESP and CONF match.  Used by __resolv_conf_get.  */
-static bool
+static __attribute__ ((noinline, noclone)) bool
 resolv_conf_matches (const struct __res_state *resp,
-                     const struct resolv_conf *conf)
+                     const struct resolv_conf *conf, FILE *fp)
 {
   /* NB: Do not compare the options, retrans, retry, ndots.  These can
      be changed by applicaiton.  */
@@ -272,23 +274,46 @@ resolv_conf_matches (const struct __res_state *resp,
       nserv = MAXNS;
     /* _ext.nscount is 0 until initialized by res_send.c.  */
     if (resp->nscount != nserv
-        && (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
-      return false;
+        || (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
+      {
+        if (fp != NULL)
+          fputs ("mismatch detected at nscount\n", fp);
+        return false;
+      }
     for (size_t i = 0; i < nserv; ++i)
       {
         if (resp->nsaddr_list[i].sin_family == 0)
           {
             if (resp->_u._ext.nsaddrs[i]->sin6_family != AF_INET6)
-              return false;
+              {
+                if (fp != NULL)
+                  fprintf
+                    (fp, "extended address family at %zu is not AF_INET6\n",
+                     i);
+                return false;
+              }
             if (!same_address ((struct sockaddr *) resp->_u._ext.nsaddrs[i],
                                conf->nameserver_list[i]))
-              return false;
+              {
+                if (fp != NULL)
+                  fprintf (fp, "extended address %zu does not match\n", i);
+                return false;
+              }
           }
         else if (resp->nsaddr_list[i].sin_family != AF_INET)
-          return false;
+          {
+            if (fp != NULL)
+              fprintf
+                (fp, "basic address family at %zu is not AF_INET\n", i);
+            return false;
+          }
         else if (!same_address ((struct sockaddr *) &resp->nsaddr_list[i],
                                 conf->nameserver_list[i]))
-          return false;
+          {
+            if (fp != NULL)
+              fprintf (fp, "basic address %zu does not match\n", i);
+            return false;
+          }
       }
   }
 
@@ -297,7 +322,11 @@ resolv_conf_matches (const struct __res_state *resp,
   {
     if (!(resp->dnsrch[0] == resp->defdname
           && resp->dnsrch[MAXDNSRCH] == NULL))
-      return false;
+      {
+        if (fp != NULL)
+          fputs ("mismatch detected at dnsrch\n", fp);
+        return false;
+      }
     size_t search_list_size = 0;
     for (size_t i = 0; i < conf->search_list_size; ++i)
       {
@@ -305,7 +334,11 @@ resolv_conf_matches (const struct __res_state *resp,
           {
             search_list_size += strlen (resp->dnsrch[i]) + 1;
             if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
-              return false;
+              {
+                if (fp != NULL)
+                  fprintf (fp, "mismatch at search entry %zu\n", i);
+                return false;
+              }
           }
         else
           {
@@ -316,6 +349,8 @@ resolv_conf_matches (const struct __res_state *resp,
             if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch))
               break;
             /* Otherwise, a mismatch indicates a match failure.  */
+            if (fp != NULL)
+              fprintf (fp, "mismatch at search entry %zu (truncation)\n", i);
             return false;
           }
       }
@@ -326,10 +361,20 @@ resolv_conf_matches (const struct __res_state *resp,
     size_t nsort = conf->sort_list_size;
     if (nsort > MAXRESOLVSORT)
       nsort = MAXRESOLVSORT;
+    if (resp->nsort != nsort)
+      {
+        if (fp != NULL)
+          fputs ("mismatch detected at nsort\n", fp);
+        return false;
+      }
     for (size_t i = 0; i < nsort; ++i)
       if (resp->sort_list[i].addr.s_addr != conf->sort_list[i].addr.s_addr
           || resp->sort_list[i].mask != conf->sort_list[i].mask)
-        return false;
+        {
+          if (fp != NULL)
+            fprintf (fp, "mismatch at sort list entry %zu\n", i);
+          return false;
+        }
   }
 
   return true;
@@ -341,7 +386,7 @@ __resolv_conf_get (struct __res_state *resp)
   struct resolv_conf *conf = resolv_conf_get_1 (resp);
   if (conf == NULL)
     return NULL;
-  if (resolv_conf_matches (resp, conf))
+  if (resolv_conf_matches (resp, conf, NULL))
     return conf;
   __resolv_conf_put (conf);
   return NULL;
@@ -462,8 +507,116 @@ __resolv_conf_allocate (const struct resolv_conf *init)
   return conf;
 }
 
+static void
+format_sockaddr (const void *ptr, FILE *fp)
+{
+  const struct sockaddr *sa = ptr;
+  char addr[50];
+  uint16_t port;
+  switch (sa->sa_family)
+    {
+    case AF_INET:
+      {
+        const struct sockaddr_in *sin = ptr;
+        if (inet_ntop (AF_INET, &sin->sin_addr, addr, sizeof (addr)) == NULL)
+          {
+            fputs ("<invalid>", fp);
+            return;
+          }
+        port = sin->sin_port;
+        break;
+      }
+    case AF_INET6:
+      {
+        const struct sockaddr_in6 *sin6 = ptr;
+        if (inet_ntop (AF_INET6, &sin6->sin6_addr, addr, sizeof (addr)) == NULL)
+          {
+            fputs ("<invalid>", fp);
+            return;
+          }
+        port = sin6->sin6_port;
+        break;
+      }
+    default:
+      fprintf (fp, "<invalid address family %d>", sa->sa_family);
+      return;
+    }
+  fprintf (fp, "[%s]:%u", addr, ntohs (port));
+}
+
+static void
+format_ipv4 (uint32_t addr, FILE *fp)
+{
+  char buf[50];
+  if (inet_ntop (AF_INET, &addr, buf, sizeof (buf)) == NULL)
+    fputs ("<invalid>", fp);
+  else
+    fputs (buf, fp);
+}
+
+static void
+print_conf (const struct resolv_conf *conf, FILE *fp)
+{
+  fprintf (fp, "resolv_conf options: 0x%08x\n", conf->options);
+  for (size_t i = 0; i < conf->nameserver_list_size; ++i)
+    {
+      fprintf (fp, "resolv_conf nameserver %zu: ", i);
+      format_sockaddr (conf->nameserver_list[i], fp);
+      _IO_putc ('\n', fp);
+    }
+  for (size_t i = 0; i < conf->search_list_size; ++i)
+    fprintf (fp, "resolv_conf search list %zu: %s\n", i, conf->search_list[i]);
+  for (size_t i = 0; i < conf->sort_list_size; ++i)
+    {
+      fprintf (fp, "resolv_conf sortlist %zu: ", i);
+      format_ipv4 (conf->sort_list[i].addr.s_addr, fp);
+      _IO_putc ('/', fp);
+      format_ipv4 (conf->sort_list[i].mask, fp);
+      _IO_putc ('\n', fp);
+    }
+}
+
+static void
+print_resp (const struct __res_state *resp, FILE *fp)
+{
+  fprintf (fp, "_res options: 0x%08lx\n", resp->options);
+  fprintf (fp, "_res nscount: %d\n", resp->nscount);
+  for (size_t i = 0; i < resp->nscount; ++i)
+    {
+      fprintf (fp, "_res nameserver %zu: ", i);
+      format_sockaddr (&resp->nsaddr_list[i], fp);
+      _IO_putc ('\n', fp);
+    }
+  fprintf (fp, "_res _ext nscount: %d\n", resp->_u._ext.nscount);
+  for (size_t i = 0; i < resp->_u._ext.nscount; ++i)
+    {
+      fprintf (fp, "_res _ext nameserver %zu: ", i);
+      if (resp->_u._ext.nsaddrs[i] == NULL)
+        fputs ("(null)", fp);
+      else
+        format_sockaddr (resp->_u._ext.nsaddrs[i], fp);
+      _IO_putc ('\n', fp);
+    }
+  fprintf (fp, "_res default domain: %s\n", resp->defdname);
+  for (size_t i = 0; i < MAXDNSRCH; ++i)
+    if (resp->dnsrch[i] == NULL)
+      break;
+    else
+      fprintf (fp, "_res search list %zu: %s\n", i, resp->dnsrch[i]);
+  fprintf (fp, "_res nsort: %d\n", resp->nsort);
+  for (size_t i = 0; i < resp->nsort; ++i)
+    {
+      fprintf (fp, "_res sortlist %zu: ", i);
+      format_ipv4 (resp->sort_list[i].addr.s_addr, fp);
+      _IO_putc ('/', fp);
+      format_ipv4 (resp->sort_list[i].mask, fp);
+      _IO_putc ('\n', fp);
+    }
+}
+
 /* Update *RESP from the extended state.  */
-static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
+static __attribute__ ((nonnull (1, 2), warn_unused_result,
+                       noinline, noclone)) bool
 update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
 {
   resp->defdname[0] = '\0';
@@ -549,7 +702,18 @@ update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
 
   /* The overlapping parts of both configurations should agree after
      initialization.  */
-  assert (resolv_conf_matches (resp, conf));
+  if (!resolv_conf_matches (resp, conf, NULL))
+    {
+      char *buffer = NULL;
+      size_t buffer_length = 0;
+      FILE *fp = __open_memstream (&buffer, &buffer_length);
+      fputs ("Fatal glibc error: resolv.conf mismatch after loading\n", fp);
+      print_conf (conf, fp);
+      print_resp (resp, fp);
+      resolv_conf_matches (resp, conf, fp);
+      fclose (fp);
+      __libc_fatal (buffer);
+    }
   return true;
 }
 
-- 
2.9.4

