Blame support/resolv_test.c

Packit 6c4009
/* DNS test framework and libresolv redirection.
Packit 6c4009
   Copyright (C) 2016-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <support/resolv_test.h>
Packit 6c4009
Packit 6c4009
#include <arpa/inet.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <fcntl.h>
Packit 6c4009
#include <nss.h>
Packit 6c4009
#include <resolv.h>
Packit 6c4009
#include <search.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <support/check.h>
Packit 6c4009
#include <support/namespace.h>
Packit 6c4009
#include <support/support.h>
Packit 6c4009
#include <support/test-driver.h>
Packit 6c4009
#include <support/xsocket.h>
Packit 6c4009
#include <support/xthread.h>
Packit 6c4009
#include <support/xunistd.h>
Packit 6c4009
#include <sys/uio.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
Packit 6c4009
/* Response builder.  */
Packit 6c4009
Packit 6c4009
enum
Packit 6c4009
  {
Packit 6c4009
    max_response_length = 65536
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
/* Used for locating domain names containing for the purpose of
Packit 6c4009
   forming compression references.  */
Packit 6c4009
struct compressed_name
Packit 6c4009
{
Packit 6c4009
  uint16_t offset;
Packit 6c4009
  unsigned char length;
Packit 6c4009
  unsigned char name[];         /* Without terminating NUL.  */
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
static struct compressed_name *
Packit 6c4009
allocate_compressed_name (const unsigned char *encoded, unsigned int offset)
Packit 6c4009
{
Packit 6c4009
  /* Compute the length of the domain name.  */
Packit 6c4009
  size_t length;
Packit 6c4009
  {
Packit 6c4009
    const unsigned char *p;
Packit 6c4009
    for (p = encoded; *p != '\0';)
Packit 6c4009
      {
Packit 6c4009
        /* No compression references are allowed.  */
Packit 6c4009
        TEST_VERIFY (*p <= 63);
Packit 6c4009
        /* Skip over the label.  */
Packit 6c4009
        p += 1 + *p;
Packit 6c4009
      }
Packit 6c4009
    length = p - encoded;
Packit 6c4009
    ++length;                   /* For the terminating NUL byte.  */
Packit 6c4009
  }
Packit 6c4009
  TEST_VERIFY_EXIT (length <= 255);
Packit 6c4009
Packit 6c4009
  struct compressed_name *result
Packit 6c4009
    = xmalloc (offsetof (struct compressed_name, name) + length);
Packit 6c4009
  result->offset = offset;
Packit 6c4009
  result->length = length;
Packit 6c4009
  memcpy (result->name, encoded, length);
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Convert CH to lower case.  Only change letters in the ASCII
Packit 6c4009
   range.  */
Packit 6c4009
static inline unsigned char
Packit 6c4009
ascii_tolower (unsigned char ch)
Packit 6c4009
{
Packit 6c4009
  if ('A' <= ch && ch <= 'Z')
Packit 6c4009
    return ch - 'A' + 'a';
Packit 6c4009
  else
Packit 6c4009
    return ch;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Compare both names, for use with tsearch.  The order is arbitrary,
Packit 6c4009
   but the comparison is case-insenstive.  */
Packit 6c4009
static int
Packit 6c4009
compare_compressed_name (const void *left, const void *right)
Packit 6c4009
{
Packit 6c4009
  const struct compressed_name *crleft = left;
Packit 6c4009
  const struct compressed_name *crright = right;
Packit 6c4009
Packit 6c4009
  if (crleft->length != crright->length)
Packit 6c4009
    /* The operands are converted to int before the subtraction.  */
Packit 6c4009
    return crleft->length - crright->length;
Packit 6c4009
Packit 6c4009
  const unsigned char *nameleft = crleft->name;
Packit 6c4009
  const unsigned char *nameright = crright->name;
Packit 6c4009
Packit 6c4009
  while (true)
Packit 6c4009
    {
Packit 6c4009
      int lenleft = *nameleft++;
Packit 6c4009
      int lenright = *nameright++;
Packit 6c4009
Packit 6c4009
      /* Labels must not e compression references.  */
Packit 6c4009
      TEST_VERIFY (lenleft <= 63);
Packit 6c4009
      TEST_VERIFY (lenright <= 63);
Packit 6c4009
Packit 6c4009
      if (lenleft != lenright)
Packit 6c4009
        return left - right;
Packit 6c4009
      if (lenleft == 0)
Packit 6c4009
        /* End of name reached without spotting a difference.  */
Packit 6c4009
        return 0;
Packit 6c4009
      /* Compare the label in a case-insenstive manner.  */
Packit 6c4009
      const unsigned char *endnameleft = nameleft + lenleft;
Packit 6c4009
      while (nameleft < endnameleft)
Packit 6c4009
        {
Packit 6c4009
          int l = *nameleft++;
Packit 6c4009
          int r = *nameright++;
Packit 6c4009
          if (l != r)
Packit 6c4009
            {
Packit 6c4009
              l = ascii_tolower (l);
Packit 6c4009
              r = ascii_tolower (r);
Packit 6c4009
              if (l != r)
Packit 6c4009
                return l - r;
Packit 6c4009
            }
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct resolv_response_builder
Packit 6c4009
{
Packit 6c4009
  const unsigned char *query_buffer;
Packit 6c4009
  size_t query_length;
Packit 6c4009
Packit 6c4009
  size_t offset;                /* Bytes written so far in buffer.  */
Packit 6c4009
  ns_sect section;              /* Current section in the DNS packet.  */
Packit 6c4009
  unsigned int truncate_bytes;  /* Bytes to remove at end of response. */
Packit 6c4009
  bool drop;                    /* Discard generated response.  */
Packit 6c4009
  bool close;                   /* Close TCP client connection.  */
Packit 6c4009
Packit 6c4009
  /* Offset of the two-byte RDATA length field in the currently
Packit 6c4009
     written RDATA sub-structure.  0 if no RDATA is being written.  */
Packit 6c4009
  size_t current_rdata_offset;
Packit 6c4009
Packit 6c4009
  /* tsearch tree for locating targets for label compression.  */
Packit 6c4009
  void *compression_offsets;
Packit 6c4009
Packit 6c4009
  /* Must be last.  Not zeroed for performance reasons.  */
Packit 6c4009
  unsigned char buffer[max_response_length];
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Response builder. */
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_init (struct resolv_response_builder *b,
Packit 6c4009
                      struct resolv_response_flags flags)
Packit 6c4009
{
Packit 6c4009
  if (b->offset > 0)
Packit 6c4009
    FAIL_EXIT1 ("response_init: called at offset %zu", b->offset);
Packit 6c4009
  if (b->query_length < 12)
Packit 6c4009
    FAIL_EXIT1 ("response_init called for a query of size %zu",
Packit 6c4009
                b->query_length);
Packit 6c4009
  if (flags.rcode > 15)
Packit 6c4009
    FAIL_EXIT1 ("response_init: invalid RCODE %u", flags.rcode);
Packit 6c4009
Packit 6c4009
  /* Copy the transaction ID.  */
Packit 6c4009
  b->buffer[0] = b->query_buffer[0];
Packit 6c4009
  b->buffer[1] = b->query_buffer[1];
Packit 6c4009
Packit 6c4009
  /* Initialize the flags.  */
Packit 6c4009
  b->buffer[2] = 0x80;                       /* Mark as response.   */
Packit 6c4009
  b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit.  */
Packit 6c4009
  if (flags.tc)
Packit 6c4009
    b->buffer[2] |= 0x02;
Packit Service 1cdea5
  b->buffer[3] = flags.rcode;
Packit Service 1cdea5
  if (!flags.clear_ra)
Packit Service 1cdea5
    b->buffer[3] |= 0x80;
Packit Service 0adcba
  if (flags.ad)
Packit Service 0adcba
    b->buffer[3] |= 0x20;
Packit 6c4009
Packit 6c4009
  /* Fill in the initial section count values.  */
Packit 6c4009
  b->buffer[4] = flags.qdcount >> 8;
Packit 6c4009
  b->buffer[5] = flags.qdcount;
Packit 6c4009
  b->buffer[6] = flags.ancount >> 8;
Packit 6c4009
  b->buffer[7] = flags.ancount;
Packit 6c4009
  b->buffer[8] = flags.nscount >> 8;
Packit 6c4009
  b->buffer[9] = flags.nscount;
Packit 6c4009
  b->buffer[10] = flags.adcount >> 8;
Packit 6c4009
  b->buffer[11] = flags.adcount;
Packit 6c4009
Packit 6c4009
  b->offset = 12;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_section (struct resolv_response_builder *b, ns_sect section)
Packit 6c4009
{
Packit 6c4009
  if (b->offset == 0)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_section: response_init not called before");
Packit 6c4009
  if (section < b->section)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_section: cannot go back to previous section");
Packit 6c4009
  b->section = section;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Add a single byte to B.  */
Packit 6c4009
static inline void
Packit 6c4009
response_add_byte (struct resolv_response_builder *b, unsigned char ch)
Packit 6c4009
{
Packit 6c4009
  if (b->offset == max_response_length)
Packit 6c4009
    FAIL_EXIT1 ("DNS response exceeds 64 KiB limit");
Packit 6c4009
  b->buffer[b->offset] = ch;
Packit 6c4009
  ++b->offset;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Add a 16-bit word VAL to B, in big-endian format.  */
Packit 6c4009
static void
Packit 6c4009
response_add_16 (struct resolv_response_builder *b, uint16_t val)
Packit 6c4009
{
Packit 6c4009
  response_add_byte (b, val >> 8);
Packit 6c4009
  response_add_byte (b, val);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Increment the pers-section record counter in the packet header.  */
Packit 6c4009
static void
Packit 6c4009
response_count_increment (struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  unsigned int offset = b->section;
Packit 6c4009
  offset = 4 + 2 * offset;
Packit 6c4009
  ++b->buffer[offset + 1];
Packit 6c4009
  if (b->buffer[offset + 1] == 0)
Packit 6c4009
    {
Packit 6c4009
      /* Carry.  */
Packit 6c4009
      ++b->buffer[offset];
Packit 6c4009
      if (b->buffer[offset] == 0)
Packit 6c4009
        /* Overflow.  */
Packit 6c4009
        FAIL_EXIT1 ("too many records in section");
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_add_question (struct resolv_response_builder *b,
Packit 6c4009
                              const char *name, uint16_t class, uint16_t type)
Packit 6c4009
{
Packit 6c4009
  if (b->offset == 0)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_add_question: "
Packit 6c4009
                "resolv_response_init not called");
Packit 6c4009
  if (b->section != ns_s_qd)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_add_question: "
Packit 6c4009
                "must be called in the question section");
Packit 6c4009
Packit 6c4009
  resolv_response_add_name (b, name);
Packit 6c4009
  response_add_16 (b, type);
Packit 6c4009
  response_add_16 (b, class);
Packit 6c4009
Packit 6c4009
  response_count_increment (b);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_add_name (struct resolv_response_builder *b,
Packit 6c4009
                          const char *const origname)
Packit 6c4009
{
Packit 6c4009
  unsigned char encoded_name[NS_MAXDNAME];
Packit 6c4009
  if (ns_name_pton (origname, encoded_name, sizeof (encoded_name)) < 0)
Packit 6c4009
    FAIL_EXIT1 ("ns_name_pton (\"%s\"): %m", origname);
Packit 6c4009
Packit 6c4009
  /* Copy the encoded name into the output buffer, apply compression
Packit 6c4009
     where possible.  */
Packit 6c4009
  for (const unsigned char *name = encoded_name; ;)
Packit 6c4009
    {
Packit 6c4009
      if (*name == '\0')
Packit 6c4009
        {
Packit 6c4009
          /* We have reached the end of the name.  Add the terminating
Packit 6c4009
             NUL byte.  */
Packit 6c4009
          response_add_byte (b, '\0');
Packit 6c4009
          break;
Packit 6c4009
        }
Packit 6c4009
Packit 6c4009
      /* Set to the compression target if compression is possible.  */
Packit 6c4009
      struct compressed_name *crname_target;
Packit 6c4009
Packit 6c4009
      /* Compression references can only reach the beginning of the
Packit 6c4009
         packet.  */
Packit 6c4009
      enum { compression_limit = 1 << 12 };
Packit 6c4009
Packit 6c4009
      {
Packit 6c4009
        /* The trailing part of the name to be looked up in the tree
Packit 6c4009
           with the compression targets.  */
Packit 6c4009
        struct compressed_name *crname
Packit 6c4009
          = allocate_compressed_name (name, b->offset);
Packit 6c4009
Packit 6c4009
        if (b->offset < compression_limit)
Packit 6c4009
          {
Packit 6c4009
            /* Add the name to the tree, for future compression
Packit 6c4009
               references.  */
Packit 6c4009
            void **ptr = tsearch (crname, &b->compression_offsets,
Packit 6c4009
                                  compare_compressed_name);
Packit 6c4009
            if (ptr == NULL)
Packit 6c4009
              FAIL_EXIT1 ("tsearch out of memory");
Packit 6c4009
            crname_target = *ptr;
Packit 6c4009
Packit 6c4009
            if (crname_target != crname)
Packit 6c4009
              /* The new name was not actually added to the tree.
Packit 6c4009
                 Deallocate it.  */
Packit 6c4009
              free (crname);
Packit 6c4009
            else
Packit 6c4009
              /* Signal that the tree did not yet contain the name,
Packit 6c4009
                 but keep the allocation because it is now part of the
Packit 6c4009
                 tree.  */
Packit 6c4009
              crname_target = NULL;
Packit 6c4009
          }
Packit 6c4009
        else
Packit 6c4009
          {
Packit 6c4009
            /* This name cannot be reached by a compression reference.
Packit 6c4009
               No need to add it to the tree for future reference.  */
Packit 6c4009
            void **ptr = tfind (crname, &b->compression_offsets,
Packit 6c4009
                                compare_compressed_name);
Packit 6c4009
            if (ptr != NULL)
Packit 6c4009
              crname_target = *ptr;
Packit 6c4009
            else
Packit 6c4009
              crname_target = NULL;
Packit 6c4009
            TEST_VERIFY (crname_target != crname);
Packit 6c4009
            /* Not added to the tree.  */
Packit 6c4009
            free (crname);
Packit 6c4009
          }
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
      if (crname_target != NULL)
Packit 6c4009
        {
Packit 6c4009
          /* The name is known.  Reference the previous location.  */
Packit 6c4009
          unsigned int old_offset = crname_target->offset;
Packit 6c4009
          TEST_VERIFY_EXIT (old_offset < compression_limit);
Packit 6c4009
          response_add_byte (b, 0xC0 | (old_offset >> 8));
Packit 6c4009
          response_add_byte (b, old_offset);
Packit 6c4009
          break;
Packit 6c4009
        }
Packit 6c4009
      else
Packit 6c4009
        {
Packit 6c4009
          /* The name is new.  Add this label.  */
Packit 6c4009
          unsigned int len = 1 + *name;
Packit 6c4009
          resolv_response_add_data (b, name, len);
Packit 6c4009
          name += len;
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_open_record (struct resolv_response_builder *b,
Packit 6c4009
                             const char *name,
Packit 6c4009
                             uint16_t class, uint16_t type, uint32_t ttl)
Packit 6c4009
{
Packit 6c4009
  if (b->section == ns_s_qd)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_open_record called in question section");
Packit 6c4009
  if (b->current_rdata_offset != 0)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_open_record called with open record");
Packit 6c4009
Packit 6c4009
  resolv_response_add_name (b, name);
Packit 6c4009
  response_add_16 (b, type);
Packit 6c4009
  response_add_16 (b, class);
Packit 6c4009
  response_add_16 (b, ttl >> 16);
Packit 6c4009
  response_add_16 (b, ttl);
Packit 6c4009
Packit 6c4009
  b->current_rdata_offset = b->offset;
Packit 6c4009
  /* Add room for the RDATA length.  */
Packit 6c4009
  response_add_16 (b, 0);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_close_record (struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  size_t rdata_offset = b->current_rdata_offset;
Packit 6c4009
  if (rdata_offset == 0)
Packit 6c4009
    FAIL_EXIT1 ("response_close_record called without open record");
Packit 6c4009
  size_t rdata_length = b->offset - rdata_offset - 2;
Packit 6c4009
  if (rdata_length > 65535)
Packit 6c4009
    FAIL_EXIT1 ("RDATA length %zu exceeds limit", rdata_length);
Packit 6c4009
  b->buffer[rdata_offset] = rdata_length >> 8;
Packit 6c4009
  b->buffer[rdata_offset + 1] = rdata_length;
Packit 6c4009
  response_count_increment (b);
Packit 6c4009
  b->current_rdata_offset = 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_add_data (struct resolv_response_builder *b,
Packit 6c4009
                          const void *data, size_t length)
Packit 6c4009
{
Packit 6c4009
  size_t remaining = max_response_length - b->offset;
Packit 6c4009
  if (remaining < length)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_add_data: not enough room for %zu bytes",
Packit 6c4009
                length);
Packit 6c4009
  memcpy (b->buffer + b->offset, data, length);
Packit 6c4009
  b->offset += length;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_drop (struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  b->drop = true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_close (struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  b->close = true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_response_truncate_data (struct resolv_response_builder *b, size_t count)
Packit 6c4009
{
Packit 6c4009
  if (count > 65535)
Packit 6c4009
    FAIL_EXIT1 ("resolv_response_truncate_data: argument too large: %zu",
Packit 6c4009
                count);
Packit 6c4009
  b->truncate_bytes = count;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
size_t
Packit 6c4009
resolv_response_length (const struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  return b->offset;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
unsigned char *
Packit 6c4009
resolv_response_buffer (const struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  unsigned char *result = xmalloc (b->offset);
Packit 6c4009
  memcpy (result, b->buffer, b->offset);
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit Service 5049f3
struct resolv_response_builder *
Packit Service 5049f3
resolv_response_builder_allocate (const unsigned char *query_buffer,
Packit Service 5049f3
                                  size_t query_length)
Packit 6c4009
{
Packit 6c4009
  struct resolv_response_builder *b = xmalloc (sizeof (*b));
Packit 6c4009
  memset (b, 0, offsetof (struct resolv_response_builder, buffer));
Packit 6c4009
  b->query_buffer = query_buffer;
Packit 6c4009
  b->query_length = query_length;
Packit 6c4009
  return b;
Packit 6c4009
}
Packit 6c4009
Packit Service 5049f3
void
Packit Service 5049f3
resolv_response_builder_free (struct resolv_response_builder *b)
Packit 6c4009
{
Packit 6c4009
  tdestroy (b->compression_offsets, free);
Packit 6c4009
  free (b);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* DNS query processing. */
Packit 6c4009
Packit 6c4009
/* Data extracted from the question section of a DNS packet.  */
Packit 6c4009
struct query_info
Packit 6c4009
{
Packit 6c4009
  char qname[MAXDNAME];
Packit 6c4009
  uint16_t qclass;
Packit 6c4009
  uint16_t qtype;
Packit 6c4009
  struct resolv_edns_info edns;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Update *INFO from the specified DNS packet.  */
Packit 6c4009
static void
Packit 6c4009
parse_query (struct query_info *info,
Packit 6c4009
             const unsigned char *buffer, size_t length)
Packit 6c4009
{
Packit 6c4009
  HEADER hd;
Packit 6c4009
  _Static_assert (sizeof (hd) == 12, "DNS header size");
Packit 6c4009
  if (length < sizeof (hd))
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length);
Packit 6c4009
  memcpy (&hd, buffer, sizeof (hd));
Packit 6c4009
Packit 6c4009
  if (ntohs (hd.qdcount) != 1)
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: wrong question count: %d",
Packit 6c4009
                (int) ntohs (hd.qdcount));
Packit 6c4009
  if (ntohs (hd.ancount) != 0)
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: wrong answer count: %d",
Packit 6c4009
                (int) ntohs (hd.ancount));
Packit 6c4009
  if (ntohs (hd.nscount) != 0)
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: wrong authority count: %d",
Packit 6c4009
                (int) ntohs (hd.nscount));
Packit 6c4009
  if (ntohs (hd.arcount) > 1)
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: wrong additional count: %d",
Packit 6c4009
                (int) ntohs (hd.arcount));
Packit 6c4009
Packit 6c4009
  int ret = dn_expand (buffer, buffer + length, buffer + sizeof (hd),
Packit 6c4009
                       info->qname, sizeof (info->qname));
Packit 6c4009
  if (ret < 0)
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME");
Packit 6c4009
Packit 6c4009
  /* Obtain QTYPE and QCLASS.  */
Packit 6c4009
  size_t remaining = length - (12 + ret);
Packit 6c4009
  struct
Packit 6c4009
  {
Packit 6c4009
    uint16_t qtype;
Packit 6c4009
    uint16_t qclass;
Packit 6c4009
  } qtype_qclass;
Packit 6c4009
  if (remaining < sizeof (qtype_qclass))
Packit 6c4009
    FAIL_EXIT1 ("malformed DNS query: "
Packit 6c4009
                "query lacks QCLASS/QTYPE, QNAME: %s", info->qname);
Packit 6c4009
  memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass));
Packit 6c4009
  info->qclass = ntohs (qtype_qclass.qclass);
Packit 6c4009
  info->qtype = ntohs (qtype_qclass.qtype);
Packit 6c4009
Packit 6c4009
  memset (&info->edns, 0, sizeof (info->edns));
Packit 6c4009
  if (ntohs (hd.arcount) > 0)
Packit 6c4009
    {
Packit 6c4009
      /* Parse EDNS record.  */
Packit 6c4009
      struct __attribute__ ((packed, aligned (1)))
Packit 6c4009
      {
Packit 6c4009
        uint8_t root;
Packit 6c4009
        uint16_t rtype;
Packit 6c4009
        uint16_t payload;
Packit 6c4009
        uint8_t edns_extended_rcode;
Packit 6c4009
        uint8_t edns_version;
Packit 6c4009
        uint16_t flags;
Packit 6c4009
        uint16_t rdatalen;
Packit 6c4009
      } rr;
Packit 6c4009
      _Static_assert (sizeof (rr) == 11, "EDNS record size");
Packit 6c4009
Packit 6c4009
      if (remaining < 4 + sizeof (rr))
Packit 6c4009
        FAIL_EXIT1 ("mailformed DNS query: no room for EDNS record");
Packit 6c4009
      memcpy (&rr, buffer + 12 + ret + 4, sizeof (rr));
Packit 6c4009
      if (rr.root != 0)
Packit 6c4009
        FAIL_EXIT1 ("malformed DNS query: invalid OPT RNAME: %d\n", rr.root);
Packit 6c4009
      if (rr.rtype != htons (41))
Packit 6c4009
        FAIL_EXIT1 ("malformed DNS query: invalid OPT type: %d\n",
Packit 6c4009
                    ntohs (rr.rtype));
Packit 6c4009
      info->edns.active = true;
Packit 6c4009
      info->edns.extended_rcode = rr.edns_extended_rcode;
Packit 6c4009
      info->edns.version = rr.edns_version;
Packit 6c4009
      info->edns.flags = ntohs (rr.flags);
Packit 6c4009
      info->edns.payload_size = ntohs (rr.payload);
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Main testing framework.  */
Packit 6c4009
Packit 6c4009
/* Per-server information.  One struct is allocated for each test
Packit 6c4009
   server.  */
Packit 6c4009
struct resolv_test_server
Packit 6c4009
{
Packit 6c4009
  /* Local address of the server.  UDP and TCP use the same port.  */
Packit 6c4009
  struct sockaddr_in address;
Packit 6c4009
Packit 6c4009
  /* File descriptor of the UDP server, or -1 if this server is
Packit 6c4009
     disabled.  */
Packit 6c4009
  int socket_udp;
Packit 6c4009
Packit 6c4009
  /* File descriptor of the TCP server, or -1 if this server is
Packit 6c4009
     disabled.  */
Packit 6c4009
  int socket_tcp;
Packit 6c4009
Packit 6c4009
  /* Counter of the number of responses processed so far.  */
Packit 6c4009
  size_t response_number;
Packit 6c4009
Packit 6c4009
  /* Thread handles for the server threads (if not disabled in the
Packit 6c4009
     configuration).  */
Packit 6c4009
  pthread_t thread_udp;
Packit 6c4009
  pthread_t thread_tcp;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Main struct for keeping track of libresolv redirection and
Packit 6c4009
   testing.  */
Packit 6c4009
struct resolv_test
Packit 6c4009
{
Packit 6c4009
  /* After initialization, any access to the struct must be performed
Packit 6c4009
     while this lock is acquired.  */
Packit 6c4009
  pthread_mutex_t lock;
Packit 6c4009
Packit 6c4009
  /* Data for each test server. */
Packit 6c4009
  struct resolv_test_server servers[resolv_max_test_servers];
Packit 6c4009
Packit 6c4009
  /* Used if config.single_thread_udp is true.  */
Packit 6c4009
  pthread_t thread_udp_single;
Packit 6c4009
Packit 6c4009
  struct resolv_redirect_config config;
Packit 6c4009
  bool termination_requested;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Function implementing a server thread.  */
Packit 6c4009
typedef void (*thread_callback) (struct resolv_test *, int server_index);
Packit 6c4009
Packit 6c4009
/* Storage for thread-specific data, for passing to the
Packit 6c4009
   thread_callback function.  */
Packit 6c4009
struct thread_closure
Packit 6c4009
{
Packit 6c4009
  struct resolv_test *obj;      /* Current test object.  */
Packit 6c4009
  thread_callback callback;     /* Function to call.  */
Packit 6c4009
  int server_index;             /* Index of the implemented server.  */
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Wrap response_callback as a function which can be passed to
Packit 6c4009
   pthread_create.  */
Packit 6c4009
static void *
Packit 6c4009
thread_callback_wrapper (void *arg)
Packit 6c4009
{
Packit 6c4009
  struct thread_closure *closure = arg;
Packit 6c4009
  closure->callback (closure->obj, closure->server_index);
Packit 6c4009
  free (closure);
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Start a server thread for the specified SERVER_INDEX, implemented
Packit 6c4009
   by CALLBACK.  */
Packit 6c4009
static pthread_t
Packit 6c4009
start_server_thread (struct resolv_test *obj, int server_index,
Packit 6c4009
                     thread_callback callback)
Packit 6c4009
{
Packit 6c4009
  struct thread_closure *closure = xmalloc (sizeof (*closure));
Packit 6c4009
  *closure = (struct thread_closure)
Packit 6c4009
    {
Packit 6c4009
      .obj = obj,
Packit 6c4009
      .callback = callback,
Packit 6c4009
      .server_index = server_index,
Packit 6c4009
    };
Packit 6c4009
  return xpthread_create (NULL, thread_callback_wrapper, closure);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Process one UDP query.  Return false if a termination requested has
Packit 6c4009
   been detected.  */
Packit 6c4009
static bool
Packit 6c4009
server_thread_udp_process_one (struct resolv_test *obj, int server_index)
Packit 6c4009
{
Packit 6c4009
  unsigned char query[512];
Packit 6c4009
  struct sockaddr_storage peer;
Packit 6c4009
  socklen_t peerlen = sizeof (peer);
Packit 6c4009
  size_t length = xrecvfrom (obj->servers[server_index].socket_udp,
Packit 6c4009
                             query, sizeof (query), 0,
Packit 6c4009
                             (struct sockaddr *) &peer, &peerlen);
Packit 6c4009
  /* Check for termination.  */
Packit 6c4009
  {
Packit 6c4009
    bool termination_requested;
Packit 6c4009
    xpthread_mutex_lock (&obj->lock);
Packit 6c4009
    termination_requested = obj->termination_requested;
Packit 6c4009
    xpthread_mutex_unlock (&obj->lock);
Packit 6c4009
    if (termination_requested)
Packit 6c4009
      return false;
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
Packit 6c4009
  struct query_info qinfo;
Packit 6c4009
  parse_query (&qinfo, query, length);
Packit 6c4009
  if (test_verbose > 0)
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose > 1)
Packit 6c4009
        printf ("info: UDP server %d: incoming query:"
Packit 6c4009
                " %zd bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
Packit 6c4009
                server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype,
Packit 6c4009
                query[0], query[1]);
Packit 6c4009
      else
Packit 6c4009
        printf ("info: UDP server %d: incoming query:"
Packit 6c4009
                " %zd bytes, %s/%u/%u\n",
Packit 6c4009
                server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  struct resolv_response_context ctx =
Packit 6c4009
    {
Packit Service 5049f3
      .test = obj,
Packit Service 5049f3
      .client_address = &peer,
Packit Service 5049f3
      .client_address_length = peerlen,
Packit 6c4009
      .query_buffer = query,
Packit 6c4009
      .query_length = length,
Packit 6c4009
      .server_index = server_index,
Packit 6c4009
      .tcp = false,
Packit 6c4009
      .edns = qinfo.edns,
Packit 6c4009
    };
Packit Service 5049f3
  struct resolv_response_builder *b
Packit Service 5049f3
    = resolv_response_builder_allocate (query, length);
Packit 6c4009
  obj->config.response_callback
Packit 6c4009
    (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
Packit 6c4009
  if (b->drop)
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        printf ("info: UDP server %d: dropping response to %s/%u/%u\n",
Packit 6c4009
                server_index, qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        {
Packit 6c4009
          if (b->offset >= 12)
Packit 6c4009
            printf ("info: UDP server %d: sending response:"
Packit 6c4009
                    " %zu bytes, RCODE %d (for %s/%u/%u)\n",
Packit Service 5049f3
                    ctx.server_index, b->offset, b->buffer[3] & 0x0f,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
          else
Packit 6c4009
            printf ("info: UDP server %d: sending response: %zu bytes"
Packit 6c4009
                    " (for %s/%u/%u)\n",
Packit 6c4009
                    server_index, b->offset,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
          if (b->truncate_bytes > 0)
Packit 6c4009
            printf ("info:    truncated by %u bytes\n", b->truncate_bytes);
Packit 6c4009
        }
Packit Service 5049f3
      resolv_response_send_udp (&ctx, b);
Packit 6c4009
    }
Packit Service 5049f3
  resolv_response_builder_free (b);
Packit 6c4009
  return true;
Packit 6c4009
}
Packit 6c4009
Packit Service 5049f3
void
Packit Service 5049f3
resolv_response_send_udp (const struct resolv_response_context *ctx,
Packit Service 5049f3
                          struct resolv_response_builder *b)
Packit Service 5049f3
{
Packit Service 5049f3
  TEST_VERIFY_EXIT (!ctx->tcp);
Packit Service 5049f3
  size_t to_send = b->offset;
Packit Service 5049f3
  if (to_send < b->truncate_bytes)
Packit Service 5049f3
    to_send = 0;
Packit Service 5049f3
  else
Packit Service 5049f3
    to_send -= b->truncate_bytes;
Packit Service 5049f3
Packit Service 5049f3
  /* Ignore most errors here because the other end may have closed
Packit Service 5049f3
     the socket.  */
Packit Service 5049f3
  if (sendto (ctx->test->servers[ctx->server_index].socket_udp,
Packit Service 5049f3
              b->buffer, to_send, 0,
Packit Service 5049f3
              ctx->client_address, ctx->client_address_length) < 0)
Packit Service 5049f3
    TEST_VERIFY_EXIT (errno != EBADF);
Packit Service 5049f3
}
Packit Service 5049f3
Packit 6c4009
/* UDP thread_callback function.  Variant for one thread per
Packit 6c4009
   server.  */
Packit 6c4009
static void
Packit 6c4009
server_thread_udp (struct resolv_test *obj, int server_index)
Packit 6c4009
{
Packit 6c4009
  while (server_thread_udp_process_one (obj, server_index))
Packit 6c4009
    ;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Single-threaded UDP processing function, for the single_thread_udp
Packit 6c4009
   case.  */
Packit 6c4009
static void *
Packit 6c4009
server_thread_udp_single (void *closure)
Packit 6c4009
{
Packit 6c4009
  struct resolv_test *obj = closure;
Packit 6c4009
Packit 6c4009
  struct pollfd fds[resolv_max_test_servers];
Packit 6c4009
  for (int server_index = 0; server_index < resolv_max_test_servers;
Packit 6c4009
       ++server_index)
Packit 6c4009
    if (obj->config.servers[server_index].disable_udp)
Packit 6c4009
      fds[server_index] = (struct pollfd) {.fd = -1};
Packit 6c4009
    else
Packit 6c4009
      {
Packit 6c4009
        fds[server_index] = (struct pollfd)
Packit 6c4009
          {
Packit 6c4009
            .fd = obj->servers[server_index].socket_udp,
Packit 6c4009
            .events = POLLIN
Packit 6c4009
          };
Packit 6c4009
Packit 6c4009
        /* Make the socket non-blocking.  */
Packit 6c4009
        int flags = fcntl (obj->servers[server_index].socket_udp, F_GETFL, 0);
Packit 6c4009
        if (flags < 0)
Packit 6c4009
          FAIL_EXIT1 ("fcntl (F_GETFL): %m");
Packit 6c4009
        flags |= O_NONBLOCK;
Packit 6c4009
        if (fcntl (obj->servers[server_index].socket_udp, F_SETFL, flags) < 0)
Packit 6c4009
          FAIL_EXIT1 ("fcntl (F_SETFL): %m");
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
  while (true)
Packit 6c4009
    {
Packit 6c4009
      xpoll (fds, resolv_max_test_servers, -1);
Packit 6c4009
      for (int server_index = 0; server_index < resolv_max_test_servers;
Packit 6c4009
           ++server_index)
Packit 6c4009
        if (fds[server_index].revents != 0)
Packit 6c4009
          {
Packit 6c4009
            if (!server_thread_udp_process_one (obj, server_index))
Packit 6c4009
              goto out;
Packit 6c4009
            fds[server_index].revents = 0;
Packit 6c4009
          }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
 out:
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Start the single UDP handler thread (for the single_thread_udp
Packit 6c4009
   case).  */
Packit 6c4009
static void
Packit 6c4009
start_server_thread_udp_single (struct resolv_test *obj)
Packit 6c4009
{
Packit 6c4009
  obj->thread_udp_single
Packit 6c4009
    = xpthread_create (NULL, server_thread_udp_single, obj);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Data describing a TCP client connect.  */
Packit 6c4009
struct tcp_thread_closure
Packit 6c4009
{
Packit 6c4009
  struct resolv_test *obj;
Packit 6c4009
  int server_index;
Packit 6c4009
  int client_socket;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Read a complete DNS query packet.  If EOF_OK, an immediate
Packit 6c4009
   end-of-file condition is acceptable.  */
Packit 6c4009
static bool
Packit 6c4009
read_fully (int fd, void *buf, size_t len, bool eof_ok)
Packit 6c4009
{
Packit 6c4009
  const void *const end = buf + len;
Packit 6c4009
  while (buf < end)
Packit 6c4009
    {
Packit 6c4009
      ssize_t ret = read (fd, buf, end - buf);
Packit 6c4009
      if (ret == 0)
Packit 6c4009
        {
Packit 6c4009
          if (!eof_ok)
Packit 6c4009
            {
Packit 6c4009
              support_record_failure ();
Packit 6c4009
              printf ("error: unexpected EOF on TCP connection\n");
Packit 6c4009
            }
Packit 6c4009
          return false;
Packit 6c4009
        }
Packit 6c4009
      else if (ret < 0)
Packit 6c4009
        {
Packit 6c4009
          if (!eof_ok || errno != ECONNRESET)
Packit 6c4009
            {
Packit 6c4009
              support_record_failure ();
Packit 6c4009
              printf ("error: TCP read: %m\n");
Packit 6c4009
            }
Packit 6c4009
          return false;
Packit 6c4009
        }
Packit 6c4009
      buf += ret;
Packit 6c4009
      eof_ok = false;
Packit 6c4009
    }
Packit 6c4009
  return true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Write an array of iovecs.  Terminate the process on failure.  */
Packit 6c4009
static void
Packit 6c4009
writev_fully (int fd, struct iovec *buffers, size_t count)
Packit 6c4009
{
Packit 6c4009
  while (count > 0)
Packit 6c4009
    {
Packit 6c4009
      /* Skip zero-length write requests.  */
Packit 6c4009
      if (buffers->iov_len == 0)
Packit 6c4009
        {
Packit 6c4009
          ++buffers;
Packit 6c4009
          --count;
Packit 6c4009
          continue;
Packit 6c4009
        }
Packit 6c4009
      /* Try to rewrite the remaing buffers.  */
Packit 6c4009
      ssize_t ret = writev (fd, buffers, count);
Packit 6c4009
      if (ret < 0)
Packit 6c4009
        FAIL_EXIT1 ("writev: %m");
Packit 6c4009
      if (ret == 0)
Packit 6c4009
        FAIL_EXIT1 ("writev: invalid return value zero");
Packit 6c4009
      /* Find the buffers that were successfully written.  */
Packit 6c4009
      while (ret > 0)
Packit 6c4009
        {
Packit 6c4009
          if (count == 0)
Packit 6c4009
            FAIL_EXIT1 ("internal writev consistency failure");
Packit 6c4009
          /* Current buffer was partially written.  */
Packit 6c4009
          if (buffers->iov_len > (size_t) ret)
Packit 6c4009
            {
Packit 6c4009
              buffers->iov_base += ret;
Packit 6c4009
              buffers->iov_len -= ret;
Packit 6c4009
              ret = 0;
Packit 6c4009
            }
Packit 6c4009
          else
Packit 6c4009
            {
Packit 6c4009
              ret -= buffers->iov_len;
Packit 6c4009
              buffers->iov_len = 0;
Packit 6c4009
              ++buffers;
Packit 6c4009
              --count;
Packit 6c4009
            }
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Thread callback for handling a single established TCP connection to
Packit 6c4009
   a client.  */
Packit 6c4009
static void *
Packit 6c4009
server_thread_tcp_client (void *arg)
Packit 6c4009
{
Packit 6c4009
  struct tcp_thread_closure *closure = arg;
Packit 6c4009
Packit 6c4009
  while (true)
Packit 6c4009
    {
Packit 6c4009
      /* Read packet length.  */
Packit 6c4009
      uint16_t query_length;
Packit 6c4009
      if (!read_fully (closure->client_socket,
Packit 6c4009
                       &query_length, sizeof (query_length), true))
Packit 6c4009
        break;
Packit 6c4009
      query_length = ntohs (query_length);
Packit 6c4009
Packit 6c4009
      /* Read the packet.  */
Packit 6c4009
      unsigned char *query_buffer = xmalloc (query_length);
Packit 6c4009
      read_fully (closure->client_socket, query_buffer, query_length, false);
Packit 6c4009
Packit 6c4009
      struct query_info qinfo;
Packit 6c4009
      parse_query (&qinfo, query_buffer, query_length);
Packit 6c4009
      if (test_verbose > 0)
Packit 6c4009
        {
Packit 6c4009
          if (test_verbose > 1)
Packit 6c4009
            printf ("info: UDP server %d: incoming query:"
Packit 6c4009
                    " %d bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
Packit 6c4009
                    closure->server_index, query_length,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype,
Packit 6c4009
                    query_buffer[0], query_buffer[1]);
Packit 6c4009
          else
Packit 6c4009
            printf ("info: TCP server %d: incoming query:"
Packit 6c4009
                    " %u bytes, %s/%u/%u\n",
Packit 6c4009
                    closure->server_index, query_length,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
        }
Packit 6c4009
Packit 6c4009
      struct resolv_response_context ctx =
Packit 6c4009
        {
Packit Service 5049f3
          .test = closure->obj,
Packit 6c4009
          .query_buffer = query_buffer,
Packit 6c4009
          .query_length = query_length,
Packit 6c4009
          .server_index = closure->server_index,
Packit 6c4009
          .tcp = true,
Packit 6c4009
          .edns = qinfo.edns,
Packit 6c4009
        };
Packit Service 5049f3
      struct resolv_response_builder *b
Packit Service 5049f3
        = resolv_response_builder_allocate (query_buffer, query_length);
Packit 6c4009
      closure->obj->config.response_callback
Packit 6c4009
        (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
Packit 6c4009
      if (b->drop)
Packit 6c4009
        {
Packit 6c4009
          if (test_verbose)
Packit 6c4009
            printf ("info: TCP server %d: dropping response to %s/%u/%u\n",
Packit 6c4009
                    closure->server_index,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
        }
Packit 6c4009
      else
Packit 6c4009
        {
Packit 6c4009
          if (test_verbose)
Packit 6c4009
            printf ("info: TCP server %d: sending response: %zu bytes"
Packit 6c4009
                    " (for %s/%u/%u)\n",
Packit 6c4009
                    closure->server_index, b->offset,
Packit 6c4009
                    qinfo.qname, qinfo.qclass, qinfo.qtype);
Packit 6c4009
          uint16_t length = htons (b->offset);
Packit 6c4009
          size_t to_send = b->offset;
Packit 6c4009
          if (to_send < b->truncate_bytes)
Packit 6c4009
            to_send = 0;
Packit 6c4009
          else
Packit 6c4009
            to_send -= b->truncate_bytes;
Packit 6c4009
          struct iovec buffers[2] =
Packit 6c4009
            {
Packit 6c4009
              {&length, sizeof (length)},
Packit 6c4009
              {b->buffer, to_send}
Packit 6c4009
            };
Packit 6c4009
          writev_fully (closure->client_socket, buffers, 2);
Packit 6c4009
        }
Packit 6c4009
      bool close_flag = b->close;
Packit Service 5049f3
      resolv_response_builder_free (b);
Packit 6c4009
      free (query_buffer);
Packit 6c4009
      if (close_flag)
Packit 6c4009
        break;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  xclose (closure->client_socket);
Packit 6c4009
  free (closure);
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* thread_callback for the TCP case.  Accept connections and create a
Packit 6c4009
   new thread for each client.  */
Packit 6c4009
static void
Packit 6c4009
server_thread_tcp (struct resolv_test *obj, int server_index)
Packit 6c4009
{
Packit 6c4009
  while (true)
Packit 6c4009
    {
Packit 6c4009
      /* Get the client conenction.  */
Packit 6c4009
      int client_socket = xaccept
Packit 6c4009
        (obj->servers[server_index].socket_tcp, NULL, NULL);
Packit 6c4009
Packit 6c4009
      /* Check for termination.  */
Packit 6c4009
      xpthread_mutex_lock (&obj->lock);
Packit 6c4009
      if (obj->termination_requested)
Packit 6c4009
        {
Packit 6c4009
          xpthread_mutex_unlock (&obj->lock);
Packit 6c4009
          xclose (client_socket);
Packit 6c4009
          break;
Packit 6c4009
        }
Packit 6c4009
      xpthread_mutex_unlock (&obj->lock);
Packit 6c4009
Packit 6c4009
      /* Spawn a new thread for handling this connection.  */
Packit 6c4009
      struct tcp_thread_closure *closure = xmalloc (sizeof (*closure));
Packit 6c4009
      *closure = (struct tcp_thread_closure)
Packit 6c4009
        {
Packit 6c4009
          .obj = obj,
Packit 6c4009
          .server_index = server_index,
Packit 6c4009
          .client_socket = client_socket,
Packit 6c4009
        };
Packit 6c4009
Packit 6c4009
      pthread_t thr
Packit 6c4009
        = xpthread_create (NULL, server_thread_tcp_client, closure);
Packit 6c4009
      /* TODO: We should keep track of this thread so that we can
Packit 6c4009
         block in resolv_test_end until it has exited.  */
Packit 6c4009
      xpthread_detach (thr);
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Create UDP and TCP server sockets.  */
Packit 6c4009
static void
Packit 6c4009
make_server_sockets (struct resolv_test_server *server)
Packit 6c4009
{
Packit 6c4009
  while (true)
Packit 6c4009
    {
Packit 6c4009
      server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Packit 6c4009
      server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
Packit 6c4009
Packit 6c4009
      /* Pick the address for the UDP socket.  */
Packit 6c4009
      server->address = (struct sockaddr_in)
Packit 6c4009
        {
Packit 6c4009
          .sin_family = AF_INET,
Packit 6c4009
          .sin_addr = {.s_addr = htonl (INADDR_LOOPBACK)}
Packit 6c4009
        };
Packit 6c4009
      xbind (server->socket_udp,
Packit 6c4009
             (struct sockaddr *)&server->address, sizeof (server->address));
Packit 6c4009
Packit 6c4009
      /* Retrieve the address. */
Packit 6c4009
      socklen_t addrlen = sizeof (server->address);
Packit 6c4009
      xgetsockname (server->socket_udp,
Packit 6c4009
                    (struct sockaddr *)&server->address, &addrlen);
Packit 6c4009
Packit 6c4009
      /* Bind the TCP socket to the same address.  */
Packit 6c4009
      {
Packit 6c4009
        int on = 1;
Packit 6c4009
        xsetsockopt (server->socket_tcp, SOL_SOCKET, SO_REUSEADDR,
Packit 6c4009
                     &on, sizeof (on));
Packit 6c4009
      }
Packit 6c4009
      if (bind (server->socket_tcp,
Packit 6c4009
                (struct sockaddr *)&server->address,
Packit 6c4009
                sizeof (server->address)) != 0)
Packit 6c4009
        {
Packit 6c4009
          /* Port collision.  The UDP bind succeeded, but the TCP BIND
Packit 6c4009
             failed.  We assume here that the kernel will pick the
Packit 6c4009
             next local UDP address randomly.  */
Packit 6c4009
          if (errno == EADDRINUSE)
Packit 6c4009
            {
Packit 6c4009
              xclose (server->socket_udp);
Packit 6c4009
              xclose (server->socket_tcp);
Packit 6c4009
              continue;
Packit 6c4009
            }
Packit 6c4009
          FAIL_EXIT1 ("TCP bind: %m");
Packit 6c4009
        }
Packit 6c4009
      xlisten (server->socket_tcp, 5);
Packit 6c4009
      break;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Like make_server_sockets, but the caller supplies the address to
Packit 6c4009
   use.  */
Packit 6c4009
static void
Packit 6c4009
make_server_sockets_for_address (struct resolv_test_server *server,
Packit 6c4009
                                 const struct sockaddr *addr)
Packit 6c4009
{
Packit 6c4009
  server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Packit 6c4009
  server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
Packit 6c4009
Packit 6c4009
  if (addr->sa_family == AF_INET)
Packit 6c4009
    server->address = *(const struct sockaddr_in *) addr;
Packit 6c4009
  else
Packit 6c4009
    /* We cannot store the server address in the socket.  This should
Packit 6c4009
       not matter if disable_redirect is used.  */
Packit 6c4009
    server->address = (struct sockaddr_in) { .sin_family = 0, };
Packit 6c4009
Packit 6c4009
  xbind (server->socket_udp,
Packit 6c4009
         (struct sockaddr *)&server->address, sizeof (server->address));
Packit 6c4009
  xbind (server->socket_tcp,
Packit 6c4009
         (struct sockaddr *)&server->address, sizeof (server->address));
Packit 6c4009
  xlisten (server->socket_tcp, 5);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* One-time initialization of NSS.  */
Packit 6c4009
static void
Packit 6c4009
resolv_redirect_once (void)
Packit 6c4009
{
Packit 6c4009
  /* Only use nss_dns.  */
Packit 6c4009
  __nss_configure_lookup ("hosts", "dns");
Packit 6c4009
  __nss_configure_lookup ("networks", "dns");
Packit 6c4009
  /* Enter a network namespace for isolation and firewall state
Packit 6c4009
     cleanup.  The tests will still work if these steps fail, but they
Packit 6c4009
     may be less reliable.  */
Packit 6c4009
  support_become_root ();
Packit 6c4009
  support_enter_network_namespace ();
Packit 6c4009
}
Packit 6c4009
pthread_once_t resolv_redirect_once_var = PTHREAD_ONCE_INIT;
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_test_init (void)
Packit 6c4009
{
Packit 6c4009
  /* Perform one-time initialization of NSS.  */
Packit 6c4009
  xpthread_once (&resolv_redirect_once_var, resolv_redirect_once);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Copy the search path from CONFIG.search to the _res object.  */
Packit 6c4009
static void
Packit 6c4009
set_search_path (struct resolv_redirect_config config)
Packit 6c4009
{
Packit 6c4009
  memset (_res.defdname, 0, sizeof (_res.defdname));
Packit 6c4009
  memset (_res.dnsrch, 0, sizeof (_res.dnsrch));
Packit 6c4009
Packit 6c4009
  char *current = _res.defdname;
Packit 6c4009
  char *end = current + sizeof (_res.defdname);
Packit 6c4009
Packit 6c4009
  for (unsigned int i = 0;
Packit 6c4009
       i < sizeof (config.search) / sizeof (config.search[0]); ++i)
Packit 6c4009
    {
Packit 6c4009
      if (config.search[i] == NULL)
Packit 6c4009
        continue;
Packit 6c4009
Packit 6c4009
      size_t length = strlen (config.search[i]) + 1;
Packit 6c4009
      size_t remaining = end - current;
Packit 6c4009
      TEST_VERIFY_EXIT (length <= remaining);
Packit 6c4009
      memcpy (current, config.search[i], length);
Packit 6c4009
      _res.dnsrch[i] = current;
Packit 6c4009
      current += length;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct resolv_test *
Packit 6c4009
resolv_test_start (struct resolv_redirect_config config)
Packit 6c4009
{
Packit 6c4009
  /* Apply configuration defaults.  */
Packit 6c4009
  if (config.nscount == 0)
Packit 6c4009
    config.nscount = resolv_max_test_servers;
Packit 6c4009
Packit 6c4009
  struct resolv_test *obj = xmalloc (sizeof (*obj));
Packit 6c4009
  *obj = (struct resolv_test) {
Packit 6c4009
    .config = config,
Packit 6c4009
    .lock = PTHREAD_MUTEX_INITIALIZER,
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
  if (!config.disable_redirect)
Packit 6c4009
    resolv_test_init ();
Packit 6c4009
Packit 6c4009
  /* Create all the servers, to reserve the necessary ports.  */
Packit 6c4009
  for (int server_index = 0; server_index < config.nscount; ++server_index)
Packit 6c4009
    if (config.disable_redirect && config.server_address_overrides != NULL)
Packit 6c4009
      make_server_sockets_for_address
Packit 6c4009
        (obj->servers + server_index,
Packit 6c4009
         config.server_address_overrides[server_index]);
Packit 6c4009
    else
Packit 6c4009
      make_server_sockets (obj->servers + server_index);
Packit 6c4009
Packit 6c4009
  /* Start server threads.  Disable the server ports, as
Packit 6c4009
     requested.  */
Packit 6c4009
  for (int server_index = 0; server_index < config.nscount; ++server_index)
Packit 6c4009
    {
Packit 6c4009
      struct resolv_test_server *server = obj->servers + server_index;
Packit 6c4009
      if (config.servers[server_index].disable_udp)
Packit 6c4009
        {
Packit 6c4009
          xclose (server->socket_udp);
Packit 6c4009
          server->socket_udp = -1;
Packit 6c4009
        }
Packit 6c4009
      else if (!config.single_thread_udp)
Packit 6c4009
        server->thread_udp = start_server_thread (obj, server_index,
Packit 6c4009
                                                  server_thread_udp);
Packit 6c4009
      if (config.servers[server_index].disable_tcp)
Packit 6c4009
        {
Packit 6c4009
          xclose (server->socket_tcp);
Packit 6c4009
          server->socket_tcp = -1;
Packit 6c4009
        }
Packit 6c4009
      else
Packit 6c4009
        server->thread_tcp = start_server_thread (obj, server_index,
Packit 6c4009
                                                  server_thread_tcp);
Packit 6c4009
    }
Packit 6c4009
  if (config.single_thread_udp)
Packit 6c4009
    start_server_thread_udp_single (obj);
Packit 6c4009
Packit 6c4009
  if (config.disable_redirect)
Packit 6c4009
    return obj;
Packit 6c4009
Packit 6c4009
  int timeout = 1;
Packit 6c4009
Packit 6c4009
  /* Initialize libresolv.  */
Packit 6c4009
  TEST_VERIFY_EXIT (res_init () == 0);
Packit 6c4009
Packit 6c4009
  /* Disable IPv6 name server addresses.  The code below only
Packit 6c4009
     overrides the IPv4 addresses.  */
Packit 6c4009
  __res_iclose (&_res, true);
Packit 6c4009
  _res._u._ext.nscount = 0;
Packit 6c4009
Packit 6c4009
  /* Redirect queries to the server socket.  */
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    {
Packit 6c4009
      printf ("info: old timeout value: %d\n", _res.retrans);
Packit 6c4009
      printf ("info: old retry attempt value: %d\n", _res.retry);
Packit 6c4009
      printf ("info: old _res.options: 0x%lx\n", _res.options);
Packit 6c4009
      printf ("info: old _res.nscount value: %d\n", _res.nscount);
Packit 6c4009
      printf ("info: old _res.ndots value: %d\n", _res.ndots);
Packit 6c4009
    }
Packit 6c4009
  _res.retrans = timeout;
Packit 6c4009
  _res.retry = 4;
Packit 6c4009
  _res.nscount = config.nscount;
Packit 6c4009
  _res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
Packit 6c4009
  _res.ndots = 1;
Packit 6c4009
  if (test_verbose)
Packit 6c4009
    {
Packit 6c4009
      printf ("info: new timeout value: %d\n", _res.retrans);
Packit 6c4009
      printf ("info: new retry attempt value: %d\n", _res.retry);
Packit 6c4009
      printf ("info: new _res.options: 0x%lx\n", _res.options);
Packit 6c4009
      printf ("info: new _res.nscount value: %d\n", _res.nscount);
Packit 6c4009
      printf ("info: new _res.ndots value: %d\n", _res.ndots);
Packit 6c4009
    }
Packit 6c4009
  for (int server_index = 0; server_index < config.nscount; ++server_index)
Packit 6c4009
    {
Packit 6c4009
      TEST_VERIFY_EXIT (obj->servers[server_index].address.sin_port != 0);
Packit 6c4009
      _res.nsaddr_list[server_index] = obj->servers[server_index].address;
Packit 6c4009
      if (test_verbose)
Packit 6c4009
        {
Packit 6c4009
          char buf[256];
Packit 6c4009
          TEST_VERIFY_EXIT
Packit 6c4009
            (inet_ntop (AF_INET, &obj->servers[server_index].address.sin_addr,
Packit 6c4009
                        buf, sizeof (buf)) != NULL);
Packit 6c4009
          printf ("info: server %d: %s/%u\n",
Packit 6c4009
                  server_index, buf,
Packit 6c4009
                  htons (obj->servers[server_index].address.sin_port));
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  set_search_path (config);
Packit 6c4009
Packit 6c4009
  return obj;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
resolv_test_end (struct resolv_test *obj)
Packit 6c4009
{
Packit 6c4009
  res_close ();
Packit 6c4009
Packit 6c4009
  xpthread_mutex_lock (&obj->lock);
Packit 6c4009
  obj->termination_requested = true;
Packit 6c4009
  xpthread_mutex_unlock (&obj->lock);
Packit 6c4009
Packit 6c4009
  /* Send trigger packets to unblock the server threads.  */
Packit 6c4009
  for (int server_index = 0; server_index < obj->config.nscount;
Packit 6c4009
       ++server_index)
Packit 6c4009
    {
Packit 6c4009
      if (!obj->config.servers[server_index].disable_udp)
Packit 6c4009
        {
Packit 6c4009
          int sock = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Packit 6c4009
          xsendto (sock, "", 1, 0,
Packit 6c4009
                   (struct sockaddr *) &obj->servers[server_index].address,
Packit 6c4009
                   sizeof (obj->servers[server_index].address));
Packit 6c4009
          xclose (sock);
Packit 6c4009
        }
Packit 6c4009
      if (!obj->config.servers[server_index].disable_tcp)
Packit 6c4009
        {
Packit 6c4009
          int sock = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
Packit 6c4009
          xconnect (sock,
Packit 6c4009
                    (struct sockaddr *) &obj->servers[server_index].address,
Packit 6c4009
                    sizeof (obj->servers[server_index].address));
Packit 6c4009
          xclose (sock);
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (obj->config.single_thread_udp)
Packit 6c4009
    xpthread_join (obj->thread_udp_single);
Packit 6c4009
Packit 6c4009
  /* Wait for the server threads to terminate.  */
Packit 6c4009
  for (int server_index = 0; server_index < obj->config.nscount;
Packit 6c4009
       ++server_index)
Packit 6c4009
    {
Packit 6c4009
      if (!obj->config.servers[server_index].disable_udp)
Packit 6c4009
        {
Packit 6c4009
          if (!obj->config.single_thread_udp)
Packit 6c4009
            xpthread_join (obj->servers[server_index].thread_udp);
Packit 6c4009
          xclose (obj->servers[server_index].socket_udp);
Packit 6c4009
        }
Packit 6c4009
      if (!obj->config.servers[server_index].disable_tcp)
Packit 6c4009
        {
Packit 6c4009
          xpthread_join (obj->servers[server_index].thread_tcp);
Packit 6c4009
          xclose (obj->servers[server_index].socket_tcp);
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  free (obj);
Packit 6c4009
}