/*
 * Copyright (c) 2017 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <vnet/dns/dns.h>

#include <vnet/vnet.h>
#include <vnet/fib/fib.h>
#include <vlibmemory/api.h>

#include <vnet/udp/udp.h>

#include <vnet/vnet_msg_enum.h>

#define vl_typedefs		/* define message structures */
#include <vnet/vnet_all_api_h.h>
#undef vl_typedefs

#define vl_endianfun		/* define message structures */
#include <vnet/vnet_all_api_h.h>
#undef vl_endianfun

/* instantiate all the print functions we know about */
#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
#define vl_printfun
#include <vnet/vnet_all_api_h.h>
#undef vl_printfun

#include <vlibapi/api_helper_macros.h>

dns_main_t dns_main;

static int
dns_cache_clear (dns_main_t * dm)
{
  dns_cache_entry_t *ep;

  if (dm->is_enabled == 0)
    return VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED;

  dns_cache_lock (dm);

  /* *INDENT-OFF* */
  pool_foreach (ep, dm->entries,
  ({
    vec_free (ep->name);
    vec_free (ep->pending_requests);
  }));
  /* *INDENT-ON* */

  pool_free (dm->entries);
  hash_free (dm->cache_entry_by_name);
  dm->cache_entry_by_name = hash_create_string (0, sizeof (uword));
  vec_free (dm->unresolved_entries);
  dns_cache_unlock (dm);
  return 0;
}

static int
dns_enable_disable (dns_main_t * dm, int is_enable)
{
  vlib_thread_main_t *tm = &vlib_thread_main;
  u32 n_vlib_mains = tm->n_vlib_mains;

  if (is_enable)
    {
      if (vec_len (dm->ip4_name_servers) == 0
	  && (vec_len (dm->ip6_name_servers) == 0))
	return VNET_API_ERROR_NO_NAME_SERVERS;

      if (dm->cache_entry_by_name == 0)
	{
	  if (n_vlib_mains > 1)
	    dm->cache_lock = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES,
						     CLIB_CACHE_LINE_BYTES);

	  dm->cache_entry_by_name = hash_create_string (0, sizeof (uword));
	}

      dm->is_enabled = 1;
    }
  else
    {
      dns_cache_clear (dm);
      dm->is_enabled = 0;
    }
  return 0;
}

static void vl_api_dns_enable_disable_t_handler
  (vl_api_dns_enable_disable_t * mp)
{
  vl_api_dns_enable_disable_reply_t *rmp;
  dns_main_t *dm = &dns_main;
  int rv;

  rv = dns_enable_disable (dm, mp->enable);

  REPLY_MACRO (VL_API_DNS_ENABLE_DISABLE_REPLY);
}

static int
dns6_name_server_add_del (dns_main_t * dm,
			  u8 * server_address_as_u8, int is_add)
{
  int i;
  ip6_address_t *ap;

  if (is_add)
    {
      /* Already there? done... */
      for (i = 0; i < vec_len (dm->ip6_name_servers); i++)
	{
	  if (!memcmp (dm->ip6_name_servers + i, server_address_as_u8,
		       sizeof (ip6_address_t)))
	    return 0;
	}

      vec_add2 (dm->ip6_name_servers, ap, 1);
      clib_memcpy (ap, server_address_as_u8, sizeof (*ap));
    }
  else
    {
      for (i = 0; i < vec_len (dm->ip6_name_servers); i++)
	{
	  if (!memcmp (dm->ip6_name_servers + i, server_address_as_u8,
		       sizeof (ip6_address_t)))
	    {
	      vec_delete (dm->ip6_name_servers, 1, i);
	      return 0;
	    }
	}
      return VNET_API_ERROR_NAME_SERVER_NOT_FOUND;
    }
  return 0;
}

static int
dns4_name_server_add_del (dns_main_t * dm,
			  u8 * server_address_as_u8, int is_add)
{
  int i;
  ip4_address_t *ap;

  if (is_add)
    {
      /* Already there? done... */
      for (i = 0; i < vec_len (dm->ip4_name_servers); i++)
	{
	  if (!memcmp (dm->ip4_name_servers + i, server_address_as_u8,
		       sizeof (ip4_address_t)))
	    return 0;
	}

      vec_add2 (dm->ip4_name_servers, ap, 1);
      clib_memcpy (ap, server_address_as_u8, sizeof (*ap));
    }
  else
    {
      for (i = 0; i < vec_len (dm->ip4_name_servers); i++)
	{
	  if (!memcmp (dm->ip4_name_servers + i, server_address_as_u8,
		       sizeof (ip4_address_t)))
	    {
	      vec_delete (dm->ip4_name_servers, 1, i);
	      return 0;
	    }
	}
      return VNET_API_ERROR_NAME_SERVER_NOT_FOUND;
    }
  return 0;
}

static void vl_api_dns_name_server_add_del_t_handler
  (vl_api_dns_name_server_add_del_t * mp)
{
  dns_main_t *dm = &dns_main;
  vl_api_dns_name_server_add_del_reply_t *rmp;
  int rv;

  if (mp->is_ip6)
    rv = dns6_name_server_add_del (dm, mp->server_address, mp->is_add);
  else
    rv = dns4_name_server_add_del (dm, mp->server_address, mp->is_add);

  REPLY_MACRO (VL_API_DNS_NAME_SERVER_ADD_DEL_REPLY);
}

static void
send_dns4_request (dns_main_t * dm,
		   dns_cache_entry_t * ep, ip4_address_t * server)
{
  vlib_main_t *vm = dm->vlib_main;
  f64 now = vlib_time_now (vm);
  u32 bi;
  vlib_buffer_t *b;
  ip4_header_t *ip;
  fib_prefix_t prefix;
  fib_node_index_t fei;
  u32 sw_if_index, fib_index;
  udp_header_t *udp;
  ip4_main_t *im4 = &ip4_main;
  ip_lookup_main_t *lm4 = &im4->lookup_main;
  ip_interface_address_t *ia = 0;
  ip4_address_t *src_address;
  u8 *dns_request;
  vlib_frame_t *f;
  u32 *to_next;

  ASSERT (ep->dns_request);

  /* Find a FIB path to the server */
  clib_memcpy (&prefix.fp_addr.ip4, server, sizeof (*server));
  prefix.fp_proto = FIB_PROTOCOL_IP4;
  prefix.fp_len = 32;

  fib_index = fib_table_find (prefix.fp_proto, 0 /* default VRF for now */ );
  if (fib_index == (u32) ~ 0)
    {
      clib_warning ("no fib table");
      return;
    }

  fei = fib_table_lookup (fib_index, &prefix);

  /* Couldn't find route to destination. Bail out. */
  if (fei == FIB_NODE_INDEX_INVALID)
    {
      clib_warning ("no route to DNS server");
      return;
    }

  sw_if_index = fib_entry_get_resolving_interface (fei);

  if (sw_if_index == ~0)
    {
      clib_warning
	("route to %U exists, fei %d, get_resolving_interface returned"
	 " ~0", fei, format_ip4_address, &prefix.fp_addr);
      return;
    }

  /* *INDENT-OFF* */
  foreach_ip_interface_address(lm4, ia, sw_if_index, 1 /* honor unnummbered */,
  ({
    src_address = ip_interface_address_get_address (lm4, ia);
    goto found_src_address;
  }));
  /* *INDENT-ON* */

  clib_warning ("FIB BUG");
  return;

found_src_address:

  /* Go get a buffer */
  if (vlib_buffer_alloc (dm->vlib_main, &bi, 1) != 1)
    return;

  b = vlib_get_buffer (vm, bi);
  b->current_length = sizeof (ip4_header_t) + sizeof (udp_header_t) +
    vec_len (ep->dns_request);
  b->total_length_not_including_first_buffer = 0;
  b->flags =
    VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_LOCALLY_ORIGINATED;
  vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;	/* "local0" */
  vnet_buffer (b)->sw_if_index[VLIB_TX] = 0;	/* default VRF for now */

  ip = vlib_buffer_get_current (b);
  memset (ip, 0, sizeof (*ip));
  udp = (udp_header_t *) (ip + 1);
  memset (udp, 0, sizeof (*udp));

  dns_request = (u8 *) (udp + 1);

  /* IP header */
  ip->ip_version_and_header_length = 0x45;
  ip->length = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b));
  ip->ttl = 255;
  ip->protocol = IP_PROTOCOL_UDP;
  ip->src_address.as_u32 = src_address->as_u32;
  ip->dst_address.as_u32 = server->as_u32;
  ip->checksum = ip4_header_checksum (ip);

  /* UDP header */
  udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dns_reply);
  udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dns);
  udp->length = clib_host_to_net_u16 (sizeof (udp_header_t) +
				      vec_len (ep->dns_request));
  udp->checksum = 0;

  /* The actual DNS request */
  clib_memcpy (dns_request, ep->dns_request, vec_len (ep->dns_request));

  /* Ship it to ip4_lookup */
  f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
  to_next = vlib_frame_vector_args (f);
  to_next[0] = bi;
  f->n_vectors = 1;
  vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);

  ep->retry_timer = now + 2.0;
}

static void
send_dns6_request (dns_main_t * dm,
		   dns_cache_entry_t * ep, ip6_address_t * server)
{
  vlib_main_t *vm = dm->vlib_main;
  f64 now = vlib_time_now (vm);
  u32 bi;
  vlib_buffer_t *b;
  ip6_header_t *ip;
  fib_prefix_t prefix;
  fib_node_index_t fei;
  u32 sw_if_index, fib_index;
  udp_header_t *udp;
  ip6_main_t *im6 = &ip6_main;
  ip_lookup_main_t *lm6 = &im6->lookup_main;
  ip_interface_address_t *ia = 0;
  ip6_address_t *src_address;
  u8 *dns_request;
  vlib_frame_t *f;
  u32 *to_next;
  int junk __attribute__ ((unused));

  ASSERT (ep->dns_request);

  /* Find a FIB path to the server */
  clib_memcpy (&prefix.fp_addr, server, sizeof (*server));
  prefix.fp_proto = FIB_PROTOCOL_IP6;
  prefix.fp_len = 32;

  fib_index = fib_table_find (prefix.fp_proto, 0 /* default VRF for now */ );
  if (fib_index == (u32) ~ 0)
    {
      clib_warning ("no fib table");
      return;
    }

  fei = fib_table_lookup (fib_index, &prefix);

  /* Couldn't find route to destination. Bail out. */
  if (fei == FIB_NODE_INDEX_INVALID)
    {
      clib_warning ("no route to DNS server");
    }

  sw_if_index = fib_entry_get_resolving_interface (fei);

  /* *INDENT-OFF* */
  foreach_ip_interface_address(lm6, ia, sw_if_index, 1 /* honor unnummbered */,
  ({
    src_address = ip_interface_address_get_address (lm6, ia);
    goto found_src_address;
  }));
  /* *INDENT-ON* */

  clib_warning ("FIB BUG");
  return;

found_src_address:

  /* Go get a buffer */
  if (vlib_buffer_alloc (dm->vlib_main, &bi, 1) != 1)
    return;

  b = vlib_get_buffer (vm, bi);
  b->current_length = sizeof (ip6_header_t) + sizeof (udp_header_t) +
    vec_len (ep->dns_request);
  b->total_length_not_including_first_buffer = 0;
  b->flags =
    VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_LOCALLY_ORIGINATED;

  ip = vlib_buffer_get_current (b);
  memset (ip, 0, sizeof (*ip));
  udp = (udp_header_t *) (ip + 1);
  memset (udp, 0, sizeof (*udp));

  dns_request = (u8 *) (udp + 1);

  /* IP header */
  ip->ip_version_traffic_class_and_flow_label =
    clib_host_to_net_u32 (0x6 << 28);

  ip->payload_length =
    clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b)
			  - sizeof (ip6_header_t));
  ip->hop_limit = 255;
  ip->protocol = IP_PROTOCOL_UDP;
  clib_memcpy (&ip->src_address, src_address, sizeof (ip6_address_t));
  clib_memcpy (&ip->dst_address, server, sizeof (ip6_address_t));

  /* UDP header */
  udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dns_reply);
  udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dns);
  udp->length = clib_host_to_net_u16 (sizeof (udp_header_t) +
				      vec_len (ep->dns_request));
  udp->checksum = 0;
  udp->checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b, ip, &junk);

  /* The actual DNS request */
  clib_memcpy (dns_request, ep->dns_request, vec_len (ep->dns_request));

  /* Ship it to ip6_lookup */
  f = vlib_get_frame_to_node (vm, ip6_lookup_node.index);
  to_next = vlib_frame_vector_args (f);
  to_next[0] = bi;
  f->n_vectors = 1;

  ep->retry_timer = now + 2.0;
}

/**
 * Translate "foo.com" into "0x3 f o o 0x3 c o m 0x0"
 * A historical / hysterical micro-TLV scheme. DGMS.
 */
u8 *
name_to_labels (u8 * name)
{
  int i;
  int last_label_index;
  u8 *rv;

  rv = vec_dup (name);

  /* punch in space for the first length */
  vec_insert (rv, 1, 0);
  last_label_index = 0;
  i = 1;

  while (i < vec_len (rv))
    {
      if (rv[i] == '.')
	{
	  rv[last_label_index] = (i - last_label_index) - 1;
	  if ((i - last_label_index) > 63)
	    clib_warning ("stupid name, label length %d",
			  i - last_label_index);
	  last_label_index = i;
	  rv[i] = 0;
	}
      i++;
    }
  /* Set the last real label length */
  rv[last_label_index] = (i - last_label_index) - 1;

  /*
   * Add a [sic] NULL root label. Otherwise, the name parser can't figure out
   * where to stop.
   */
  vec_add1 (rv, 0);
  return rv;
}

/**
 * arc-function for the above.
 * Translate "0x3 f o o 0x3 c o m 0x0" into "foo.com"
 * Produces a non-NULL-terminated u8 *vector. %v format is your friend.
 */
u8 *
vnet_dns_labels_to_name (u8 * label, u8 * full_text, u8 ** parse_from_here)
{
  u8 *reply = 0;
  u16 offset;
  u8 len;
  int i;

  *parse_from_here = 0;

  /* chase initial pointer? */
  if ((label[0] & 0xC0) == 0xC0)
    {
      *parse_from_here = label + 2;
      offset = ((label[0] & 0x3f) << 8) + label[1];
      label = full_text + offset;
    }

  len = *label++;

  while (len)
    {
      for (i = 0; i < len; i++)
	vec_add1 (reply, *label++);

      /* chase pointer? */
      if ((label[0] & 0xC0) == 0xC0)
	{
	  *parse_from_here = label + 2;
	  offset = ((label[0] & 0x3f) << 8) + label[1];
	  label = full_text + offset;
	}

      len = *label++;
      if (len)
	vec_add1 (reply, '.');
    }
  if (*parse_from_here == 0)
    *parse_from_here = label;
  return reply;
}

void
vnet_send_dns_request (dns_main_t * dm, dns_cache_entry_t * ep)
{
  dns_header_t *h;
  dns_query_t *qp;
  u16 tmp;
  u8 *request;
  u32 qp_offset;

  /* This can easily happen if sitting in GDB, etc. */
  if (ep->flags & DNS_CACHE_ENTRY_FLAG_VALID)
    return;

  /* Construct the dns request, if we haven't been here already */
  if (vec_len (ep->dns_request) == 0)
    {
      /*
       * Start with the variadic portion of the exercise.
       * Turn the name into a set of DNS "labels". Max length
       * per label is 63, enforce that.
       */
      request = name_to_labels (ep->name);
      qp_offset = vec_len (request);

      /* Add space for the query header */
      vec_validate (request, qp_offset + sizeof (dns_query_t) - 1);

      qp = (dns_query_t *) (request + qp_offset);

      qp->type = clib_host_to_net_u16 (DNS_TYPE_ALL);
      qp->class = clib_host_to_net_u16 (DNS_CLASS_IN);

      /* Punch in space for the dns_header_t */
      vec_insert (request, sizeof (dns_header_t), 0);

      h = (dns_header_t *) request;

      /* Transaction ID = pool index */
      h->id = clib_host_to_net_u16 (ep - dm->entries);

      /* Ask for a recursive lookup */
      tmp = DNS_RD | DNS_OPCODE_QUERY;
      h->flags = clib_host_to_net_u16 (tmp);
      h->qdcount = clib_host_to_net_u16 (1);
      h->nscount = 0;
      h->arcount = 0;

      ep->dns_request = request;
    }

  /* Work out which server / address family we're going to use */

  /* Retry using current server */
  if (ep->retry_count++ < DNS_RETRIES_PER_SERVER)
    {
      if (ep->server_af == 1 /* ip6 */ )
	{
	  if (vec_len (dm->ip6_name_servers))
	    {
	      send_dns6_request (dm, ep,
				 dm->ip6_name_servers + ep->server_rotor);
	      goto out;
	    }
	  else
	    ep->server_af = 0;
	}
      if (vec_len (dm->ip4_name_servers))
	{
	  send_dns4_request (dm, ep, dm->ip4_name_servers + ep->server_rotor);
	  goto out;
	}
    }
  else				/* switch to a new server */
    {
      ep->retry_count = 1;
      ep->server_rotor++;
      if (ep->server_af == 1 /* ip6 */ )
	{
	  if (ep->server_rotor >= vec_len (dm->ip6_name_servers))
	    {
	      ep->server_rotor = 0;
	      ep->server_af = vec_len (dm->ip4_name_servers) > 0 ? 0 : 1;
	    }
	}
      else
	{
	  if (ep->server_rotor >= vec_len (dm->ip4_name_servers))
	    {
	      ep->server_rotor = 0;
	      ep->server_af = vec_len (dm->ip6_name_servers) > 0 ? 1 : 0;
	    }
	}
    }

  if (ep->server_af == 1 /* ip6 */ )
    send_dns6_request (dm, ep, dm->ip6_name_servers + ep->server_rotor);
  else
    send_dns4_request (dm, ep, dm->ip4_name_servers + ep->server_rotor);

out:

  vlib_process_signal_event_mt (dm->vlib_main, dns_resolver_node.index,
				DNS_RESOLVER_EVENT_PENDING, 0);
}

int
vnet_dns_delete_entry_by_index_nolock (dns_main_t * dm, u32 index)
{
  dns_cache_entry_t *ep;
  int i;

  if (dm->is_enabled == 0)
    return VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED;

  if (pool_is_free_index (dm->entries, index))
    return VNET_API_ERROR_NO_SUCH_ENTRY;

  ep = pool_elt_at_index (dm->entries, index);
  if (!(ep->flags & DNS_CACHE_ENTRY_FLAG_VALID))
    {
      for (i = 0; i < vec_len (dm->unresolved_entries); i++)
	if (index == dm->unresolved_entries[i])
	  {
	    vec_delete (dm->unresolved_entries, 1, i);
	    goto found;
	  }
      clib_warning ("pool elt %d supposedly pending, but not found...",
		    index);
    }

found:
  hash_unset_mem (dm->cache_entry_by_name, ep->name);
  vec_free (ep->name);
  vec_free (ep->pending_requests);
  pool_put (dm->entries, ep);

  return 0;
}

static int
dns_delete_by_name (dns_main_t * dm, u8 * name)
{
  int rv;
  uword *p;

  if (dm->is_enabled == 0)
    return VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED;

  dns_cache_lock (dm);
  p = hash_get_mem (dm->cache_entry_by_name, name);
  if (!p)
    {
      dns_cache_unlock (dm);
      return VNET_API_ERROR_NO_SUCH_ENTRY;
    }
  rv = vnet_dns_delete_entry_by_index_nolock (dm, p[0]);

  dns_cache_unlock (dm);

  return rv;
}

static int
delete_random_entry (dns_main_t * dm)
{
  int rv;
  u32 victim_index, start_index, i;
  u32 limit;
  dns_cache_entry_t *ep;

  if (dm->is_enabled == 0)
    return VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED;

  /*
   * Silence spurious coverity warning. We know pool_elts >> 0, or
   * we wouldn't be here...
   */
#ifdef __COVERITY__
  if (pool_elts (dm->entries) == 0)
    return VNET_API_ERROR_UNSPECIFIED;
#endif

  dns_cache_lock (dm);
  limit = pool_elts (dm->entries);
  start_index = random_u32 (&dm->random_seed) % limit;

  for (i = 0; i < limit; i++)
    {
      victim_index = (start_index + i) % limit;

      if (!pool_is_free_index (dm->entries, victim_index))
	{
	  ep = pool_elt_at_index (dm->entries, victim_index);
	  /* Delete only valid, non-static entries */
	  if ((ep->flags & DNS_CACHE_ENTRY_FLAG_VALID)
	      && ((ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC) == 0))
	    {
	      rv = vnet_dns_delete_entry_by_index_nolock (dm, victim_index);
	      dns_cache_unlock (dm);
	      return rv;
	    }
	}
    }
  dns_cache_unlock (dm);

  clib_warning ("Couldn't find an entry to delete?");
  return VNET_API_ERROR_UNSPECIFIED;
}

static int
dns_add_static_entry (dns_main_t * dm, u8 * name, u8 * dns_reply_data)
{
  dns_cache_entry_t *ep;
  uword *p;
  int rv;

  if (dm->is_enabled == 0)
    return VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED;

  dns_cache_lock (dm);
  p = hash_get_mem (dm->cache_entry_by_name, name);
  if (p)
    {
      dns_cache_unlock (dm);
      return VNET_API_ERROR_ENTRY_ALREADY_EXISTS;
    }

  if (pool_elts (dm->entries) == dm->name_cache_size)
    {
      /* Will only fail if the cache is totally filled w/ static entries... */
      rv = delete_random_entry (dm);
      if (rv)
	{
	  dns_cache_unlock (dm);
	  return rv;
	}
    }

  pool_get (dm->entries, ep);
  memset (ep, 0, sizeof (*ep));

  /* Note: consumes the name vector */
  ep->name = name;
  hash_set_mem (dm->cache_entry_by_name, ep->name, ep - dm->entries);
  ep->flags = DNS_CACHE_ENTRY_FLAG_VALID | DNS_CACHE_ENTRY_FLAG_STATIC;
  ep->dns_response = dns_reply_data;

  dns_cache_unlock (dm);
  return 0;
}

int
vnet_dns_resolve_name (dns_main_t * dm, u8 * name, dns_pending_request_t * t,
		       dns_cache_entry_t ** retp)
{
  dns_cache_entry_t *ep;
  int rv;
  f64 now;
  uword *p;
  dns_pending_request_t *pr;
  int count;

  now = vlib_time_now (dm->vlib_main);

  /* In case we can't actually answer the question right now... */
  *retp = 0;

  dns_cache_lock (dm);
search_again:
  p = hash_get_mem (dm->cache_entry_by_name, name);
  if (p)
    {
      ep = pool_elt_at_index (dm->entries, p[0]);
      if (ep->flags & DNS_CACHE_ENTRY_FLAG_VALID)
	{
	  /* Has the entry expired? */
	  if (((ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC) == 0)
	      && (now > ep->expiration_time))
	    {
	      int i;
	      u32 *indices_to_delete = 0;

	      /*
	       * Take out the rest of the resolution chain
	       * This isn't optimal, but it won't happen very often.
	       */
	      while (ep)
		{
		  if ((ep->flags & DNS_CACHE_ENTRY_FLAG_CNAME))
		    {
		      vec_add1 (indices_to_delete, ep - dm->entries);

		      p = hash_get_mem (dm->cache_entry_by_name, ep->cname);
		      if (!p)
			break;
		      ep = pool_elt_at_index (dm->entries, p[0]);
		    }
		  else
		    {
		      vec_add1 (indices_to_delete, ep - dm->entries);
		      break;
		    }
		}
	      for (i = 0; i < vec_len (indices_to_delete); i++)
		{
		  /* Reenable to watch re-resolutions */
		  if (0)
		    {
		      ep = pool_elt_at_index (dm->entries,
					      indices_to_delete[i]);
		      clib_warning ("Re-resolve %s", ep->name);
		    }

		  vnet_dns_delete_entry_by_index_nolock
		    (dm, indices_to_delete[i]);
		}
	      vec_free (indices_to_delete);
	      /* Yes, kill it... */
	      goto re_resolve;
	    }

	  if (ep->flags & DNS_CACHE_ENTRY_FLAG_CNAME)
	    {
	      name = ep->cname;
	      goto search_again;
	    }

	  /* Note: caller must drop the lock! */
	  *retp = ep;
	  return (0);
	}
      else
	{
	  /*
	   * Resolution pending. Add request to the pending vector
	   * by copying the template request
	   */
	  vec_add2 (ep->pending_requests, pr, 1);
	  memcpy (pr, t, sizeof (*pr));
	  dns_cache_unlock (dm);
	  return (0);
	}
    }

re_resolve:
  if (pool_elts (dm->entries) == dm->name_cache_size)
    {
      /* Will only fail if the cache is totally filled w/ static entries... */
      rv = delete_random_entry (dm);
      if (rv)
	{
	  dns_cache_unlock (dm);
	  return rv;
	}
    }

  /* add new hash table entry */
  pool_get (dm->entries, ep);
  memset (ep, 0, sizeof (*ep));

  ep->name = format (0, "%s%c", name, 0);
  _vec_len (ep->name) = vec_len (ep->name) - 1;

  hash_set_mem (dm->cache_entry_by_name, ep->name, ep - dm->entries);

  vec_add1 (dm->unresolved_entries, ep - dm->entries);
  vec_add2 (ep->pending_requests, pr, 1);

  pr->request_type = t->request_type;

  /* Remember details so we can reply later... */
  if (t->request_type == DNS_API_PENDING_NAME_TO_IP ||
      t->request_type == DNS_API_PENDING_IP_TO_NAME)
    {
      pr->client_index = t->client_index;
      pr->client_context = t->client_context;
    }
  else
    {
      pr->client_index = ~0;
      pr->is_ip6 = t->is_ip6;
      pr->dst_port = t->dst_port;
      pr->id = t->id;
      pr->name = t->name;
      if (t->is_ip6)
	count = 16;
      else
	count = 4;
      clib_memcpy (pr->dst_address, t->dst_address, count);
    }

  vnet_send_dns_request (dm, ep);
  dns_cache_unlock (dm);
  return 0;
}

#define foreach_notification_to_move            \
_(pending_requests)

/**
 * Handle cname indirection. JFC. Called with the cache locked.
 * returns 0 if the reply is not a CNAME.
 */

int
vnet_dns_cname_indirection_nolock (dns_main_t * dm, u32 ep_index, u8 * reply)
{
  dns_header_t *h;
  dns_query_t *qp;
  dns_rr_t *rr;
  u8 *curpos;
  u8 *pos, *pos2;
  int len, i;
  u8 *cname = 0;
  u8 *request = 0;
  u32 qp_offset;
  u16 flags;
  u16 rcode;
  dns_cache_entry_t *ep, *next_ep;
  f64 now;

  h = (dns_header_t *) reply;
  flags = clib_net_to_host_u16 (h->flags);
  rcode = flags & DNS_RCODE_MASK;

  /* See if the response is OK */
  switch (rcode)
    {
    case DNS_RCODE_NO_ERROR:
      break;

    case DNS_RCODE_NAME_ERROR:
    case DNS_RCODE_FORMAT_ERROR:
    case DNS_RCODE_SERVER_FAILURE:
    case DNS_RCODE_NOT_IMPLEMENTED:
    case DNS_RCODE_REFUSED:
      return 0;
    }

  curpos = (u8 *) (h + 1);
  pos = curpos;
  len = *pos++;

  /* Skip the questions */
  for (i = 0; i < clib_net_to_host_u16 (h->qdcount); i++)
    {
      while (len)
	{
	  pos += len;
	  len = *pos++;
	}
      pos += sizeof (dns_query_t);
    }
  pos2 = pos;
  /* expect a pointer chase here for a CNAME record */
  if ((pos2[0] & 0xC0) == 0xC0)
    pos += 2;
  else
    return 0;

  rr = (dns_rr_t *) pos;

  /* This is a real record, not a CNAME record */
  if (clib_net_to_host_u16 (rr->type) != DNS_TYPE_CNAME)
    return 0;

  /* This is a CNAME record, chase the name chain. */

  /* The last request is no longer pending.. */
  for (i = 0; i < vec_len (dm->unresolved_entries); i++)
    if (ep_index == dm->unresolved_entries[i])
      {
	vec_delete (dm->unresolved_entries, 1, i);
	goto found_last_request;
      }
  clib_warning ("pool elt %d supposedly pending, but not found...", ep_index);

found_last_request:

  now = vlib_time_now (dm->vlib_main);
  cname = vnet_dns_labels_to_name (rr->rdata, reply, &pos2);
  /* Save the cname */
  vec_add1 (cname, 0);
  _vec_len (cname) -= 1;
  ep = pool_elt_at_index (dm->entries, ep_index);
  ep->cname = cname;
  ep->flags |= (DNS_CACHE_ENTRY_FLAG_CNAME | DNS_CACHE_ENTRY_FLAG_VALID);
  /* Save the response */
  ep->dns_response = reply;
  /* Set up expiration time */
  ep->expiration_time = now + clib_net_to_host_u32 (rr->ttl);

  pool_get (dm->entries, next_ep);

  /* Need to recompute ep post pool-get */
  ep = pool_elt_at_index (dm->entries, ep_index);

  memset (next_ep, 0, sizeof (*next_ep));
  next_ep->name = vec_dup (cname);
  vec_add1 (next_ep->name, 0);
  _vec_len (next_ep->name) -= 1;

  hash_set_mem (dm->cache_entry_by_name, next_ep->name,
		next_ep - dm->entries);

  /* Use the same server */
  next_ep->server_rotor = ep->server_rotor;
  next_ep->server_af = ep->server_af;

  /* Move notification data to the next name in the chain */
#define _(a) next_ep->a = ep->a; ep->a = 0;
  foreach_notification_to_move;
#undef _

  request = name_to_labels (cname);

  qp_offset = vec_len (request);

  /* Add space for the query header */
  vec_validate (request, qp_offset + sizeof (dns_query_t) - 1);

  qp = (dns_query_t *) (request + qp_offset);

  qp->type = clib_host_to_net_u16 (DNS_TYPE_ALL);
  qp->class = clib_host_to_net_u16 (DNS_CLASS_IN);

  /* Punch in space for the dns_header_t */
  vec_insert (request, sizeof (dns_header_t), 0);

  h = (dns_header_t *) request;

  /* Transaction ID = pool index */
  h->id = clib_host_to_net_u16 (next_ep - dm->entries);

  /* Ask for a recursive lookup */
  h->flags = clib_host_to_net_u16 (DNS_RD | DNS_OPCODE_QUERY);
  h->qdcount = clib_host_to_net_u16 (1);
  h->nscount = 0;
  h->arcount = 0;

  next_ep->dns_request = request;
  next_ep->retry_timer = now + 2.0;
  next_ep->retry_count = 0;

  /*
   * Enable this to watch recursive resolution happen...
   * fformat (stdout, "%U", format_dns_reply, request, 2);
   */

  vec_add1 (dm->unresolved_entries, next_ep - dm->entries);
  vnet_send_dns_request (dm, next_ep);
  return (1);
}

int
vnet_dns_response_to_reply (u8 * response,
			    vl_api_dns_resolve_name_reply_t * rmp,
			    u32 * min_ttlp)
{
  dns_header_t *h;
  dns_query_t *qp;
  dns_rr_t *rr;
  int i, limit;
  u8 len;
  u8 *curpos, *pos, *pos2;
  u16 flags;
  u16 rcode;
  u32 ttl;
  int pointer_chase;

  h = (dns_header_t *) response;
  flags = clib_net_to_host_u16 (h->flags);
  rcode = flags & DNS_RCODE_MASK;

  /* See if the response is OK, etc. */
  switch (rcode)
    {
    default:
    case DNS_RCODE_NO_ERROR:
      break;

    case DNS_RCODE_NAME_ERROR:
    case DNS_RCODE_FORMAT_ERROR:
      return VNET_API_ERROR_NAME_SERVER_NO_SUCH_NAME;

    case DNS_RCODE_SERVER_FAILURE:
    case DNS_RCODE_NOT_IMPLEMENTED:
    case DNS_RCODE_REFUSED:
      return VNET_API_ERROR_NAME_SERVER_NEXT_SERVER;
    }

  /* No answers? Loser... */
  if (clib_net_to_host_u16 (h->anscount) < 1)
    return VNET_API_ERROR_NAME_SERVER_NO_ADDRESSES;

  curpos = (u8 *) (h + 1);

  /* Skip the name we asked about */
  pos = curpos;
  len = *pos++;
  /* Should never happen, but stil... */
  if ((len & 0xC0) == 0xC0)
    curpos += 2;
  else
    {
      /* skip the name / label-set */
      while (len)
	{
	  pos += len;
	  len = *pos++;
	}
      curpos = pos;
    }
  /* Skip queries */
  limit = clib_net_to_host_u16 (h->qdcount);
  qp = (dns_query_t *) curpos;
  qp += limit;
  curpos = (u8 *) qp;

  /* Parse answers */
  limit = clib_net_to_host_u16 (h->anscount);

  for (i = 0; i < limit; i++)
    {
      pos = pos2 = curpos;
      pointer_chase = 0;

      /* Expect pointer chases in the answer section... */
      if ((pos2[0] & 0xC0) == 0xC0)
	{
	  pos = pos2 + 2;
	  pos2 = response + ((pos2[0] & 0x3f) << 8) + pos2[1];
	  pointer_chase = 1;
	}

      len = *pos2++;

      while (len)
	{
	  pos2 += len;
	  if ((pos2[0] & 0xc0) == 0xc0)
	    {
	      /*
	       * If we've already done one pointer chase,
	       * do not move the pos pointer.
	       */
	      if (pointer_chase == 0)
		pos = pos2 + 2;
	      pos2 = response + ((pos2[0] & 0x3f) << 8) + pos2[1];
	      len = *pos2++;
	      pointer_chase = 1;
	    }
	  else
	    len = *pos2++;
	}

      if (pointer_chase == 0)
	pos = pos2;

      rr = (dns_rr_t *) pos;

      switch (clib_net_to_host_u16 (rr->type))
	{
	case DNS_TYPE_A:
	  /* Collect an ip4 address. Do not pass go. Do not collect $200 */
	  memcpy (rmp->ip4_address, rr->rdata, sizeof (ip4_address_t));
	  rmp->ip4_set = 1;
	  ttl = clib_net_to_host_u32 (rr->ttl);
	  if (min_ttlp && *min_ttlp > ttl)
	    *min_ttlp = ttl;
	  break;
	case DNS_TYPE_AAAA:
	  /* Collect an ip6 address. Do not pass go. Do not collect $200 */
	  memcpy (rmp->ip6_address, rr->rdata, sizeof (ip6_address_t));
	  ttl = clib_net_to_host_u32 (rr->ttl);
	  if (min_ttlp && *min_ttlp > ttl)
	    *min_ttlp = ttl;
	  rmp->ip6_set = 1;
	  break;

	default:
	  break;
	}
      /* Might as well stop ASAP */
      if (rmp->ip4_set && rmp->ip6_set)
	break;
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      curpos = pos;
    }

  if ((rmp->ip4_set + rmp->ip6_set) == 0)
    return VNET_API_ERROR_NAME_SERVER_NO_ADDRESSES;
  return 0;
}

int
vnet_dns_response_to_name (u8 * response,
			   vl_api_dns_resolve_ip_reply_t * rmp,
			   u32 * min_ttlp)
{
  dns_header_t *h;
  dns_query_t *qp;
  dns_rr_t *rr;
  int i, limit;
  u8 len;
  u8 *curpos, *pos, *pos2;
  u16 flags;
  u16 rcode;
  u8 *name;
  u32 ttl;
  u8 *junk __attribute__ ((unused));
  int name_set = 0;
  int pointer_chase;

  h = (dns_header_t *) response;
  flags = clib_net_to_host_u16 (h->flags);
  rcode = flags & DNS_RCODE_MASK;

  /* See if the response is OK, etc. */
  switch (rcode)
    {
    default:
    case DNS_RCODE_NO_ERROR:
      break;

    case DNS_RCODE_NAME_ERROR:
    case DNS_RCODE_FORMAT_ERROR:
      return VNET_API_ERROR_NAME_SERVER_NO_SUCH_NAME;

    case DNS_RCODE_SERVER_FAILURE:
    case DNS_RCODE_NOT_IMPLEMENTED:
    case DNS_RCODE_REFUSED:
      return VNET_API_ERROR_NAME_SERVER_NEXT_SERVER;
    }

  /* No answers? Loser... */
  if (clib_net_to_host_u16 (h->anscount) < 1)
    return VNET_API_ERROR_NAME_SERVER_NO_ADDRESSES;

  curpos = (u8 *) (h + 1);

  /* Skip the name we asked about */
  pos = curpos;
  len = *pos++;
  /* Should never happen, but stil... */
  if ((len & 0xC0) == 0xC0)
    curpos += 2;
  else
    {
      /* skip the name / label-set */
      while (len)
	{
	  pos += len;
	  len = *pos++;
	}
      curpos = pos;
    }
  /* Skip queries */
  limit = clib_net_to_host_u16 (h->qdcount);
  qp = (dns_query_t *) curpos;
  qp += limit;
  curpos = (u8 *) qp;

  /* Parse answers */
  limit = clib_net_to_host_u16 (h->anscount);

  for (i = 0; i < limit; i++)
    {
      pos = pos2 = curpos;
      pointer_chase = 0;

      /* Expect pointer chases in the answer section... */
      if ((pos2[0] & 0xC0) == 0xC0)
	{
	  pos = pos2 + 2;
	  pos2 = response + ((pos2[0] & 0x3f) << 8) + pos2[1];
	  pointer_chase = 1;
	}

      len = *pos2++;

      while (len)
	{
	  pos2 += len;
	  if ((pos2[0] & 0xc0) == 0xc0)
	    {
	      /*
	       * If we've already done one pointer chase,
	       * do not move the pos pointer.
	       */
	      if (pointer_chase == 0)
		pos = pos2 + 2;
	      pos2 = response + ((pos2[0] & 0x3f) << 8) + pos2[1];
	      len = *pos2++;
	      pointer_chase = 1;
	    }
	  else
	    len = *pos2++;
	}

      if (pointer_chase == 0)
	pos = pos2;

      rr = (dns_rr_t *) pos;

      switch (clib_net_to_host_u16 (rr->type))
	{
	case DNS_TYPE_PTR:
	  name = vnet_dns_labels_to_name (rr->rdata, response, &junk);
	  memcpy (rmp->name, name, vec_len (name));
	  ttl = clib_net_to_host_u32 (rr->ttl);
	  if (min_ttlp)
	    *min_ttlp = ttl;
	  rmp->name[vec_len (name)] = 0;
	  name_set = 1;
	  break;
	default:
	  break;
	}
      /* Might as well stop ASAP */
      if (name_set == 1)
	break;
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      curpos = pos;
    }

  if (name_set == 0)
    return VNET_API_ERROR_NAME_SERVER_NO_SUCH_NAME;
  return 0;
}

static void
vl_api_dns_resolve_name_t_handler (vl_api_dns_resolve_name_t * mp)
{
  dns_main_t *dm = &dns_main;
  vl_api_dns_resolve_name_reply_t *rmp;
  dns_cache_entry_t *ep;
  dns_pending_request_t _t0, *t0 = &_t0;
  int rv;

  /* Sanitize the name slightly */
  mp->name[ARRAY_LEN (mp->name) - 1] = 0;

  t0->request_type = DNS_API_PENDING_NAME_TO_IP;
  t0->client_index = mp->client_index;
  t0->client_context = mp->context;

  rv = vnet_dns_resolve_name (dm, mp->name, t0, &ep);

  /* Error, e.g. not enabled? Tell the user */
  if (rv < 0)
    {
      REPLY_MACRO (VL_API_DNS_RESOLVE_NAME_REPLY);
      return;
    }

  /* Resolution pending? Don't reply... */
  if (ep == 0)
    return;

  /* *INDENT-OFF* */
  REPLY_MACRO2(VL_API_DNS_RESOLVE_NAME_REPLY,
  ({
    rv = vnet_dns_response_to_reply (ep->dns_response, rmp, 0 /* ttl-ptr */);
    rmp->retval = clib_host_to_net_u32 (rv);
  }));
  /* *INDENT-ON* */

  /*
   * dns_resolve_name leaves the cache locked when it returns
   * a cached result, so unlock it here.
   */
  dns_cache_unlock (dm);
}

static void
vl_api_dns_resolve_ip_t_handler (vl_api_dns_resolve_ip_t * mp)
{
  dns_main_t *dm = &dns_main;
  vl_api_dns_resolve_ip_reply_t *rmp;
  dns_cache_entry_t *ep;
  int rv;
  int i, len;
  u8 *lookup_name = 0;
  u8 digit, nybble;
  dns_pending_request_t _t0, *t0 = &_t0;

  if (mp->is_ip6)
    {
      for (i = 15; i >= 0; i--)
	{
	  digit = mp->address[i];
	  nybble = (digit & 0x0F);
	  if (nybble > 9)
	    vec_add1 (lookup_name, (nybble - 10) + 'a');
	  else
	    vec_add1 (lookup_name, nybble + '0');
	  vec_add1 (lookup_name, '.');
	  nybble = (digit & 0xF0) >> 4;
	  if (nybble > 9)
	    vec_add1 (lookup_name, (nybble - 10) + 'a');
	  else
	    vec_add1 (lookup_name, nybble + '0');
	  vec_add1 (lookup_name, '.');
	}
      len = vec_len (lookup_name);
      vec_validate (lookup_name, len + 8);
      memcpy (lookup_name + len, "ip6.arpa", 8);
    }
  else
    {
      for (i = 3; i >= 0; i--)
	{
	  digit = mp->address[i];
	  lookup_name = format (lookup_name, "%d.", digit);
	}
      lookup_name = format (lookup_name, "in-addr.arpa");
    }

  vec_add1 (lookup_name, 0);

  t0->request_type = DNS_API_PENDING_IP_TO_NAME;
  t0->client_index = mp->client_index;
  t0->client_context = mp->context;

  rv = vnet_dns_resolve_name (dm, lookup_name, t0, &ep);

  vec_free (lookup_name);

  /* Error, e.g. not enabled? Tell the user */
  if (rv < 0)
    {
      REPLY_MACRO (VL_API_DNS_RESOLVE_IP_REPLY);
      return;
    }

  /* Resolution pending? Don't reply... */
  if (ep == 0)
    return;

  /* *INDENT-OFF* */
  REPLY_MACRO2(VL_API_DNS_RESOLVE_IP_REPLY,
  ({
    rv = vnet_dns_response_to_name (ep->dns_response, rmp, 0 /* ttl-ptr */);
    rmp->retval = clib_host_to_net_u32 (rv);
  }));
  /* *INDENT-ON* */

  /*
   * vnet_dns_resolve_name leaves the cache locked when it returns
   * a cached result, so unlock it here.
   */
  dns_cache_unlock (dm);
}

#define vl_msg_name_crc_list
#include <vpp/api/vpe_all_api_h.h>
#undef vl_msg_name_crc_list

static void
setup_message_id_table (api_main_t * am)
{
#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
  foreach_vl_msg_name_crc_dns;
#undef _
}

#define foreach_dns_api_msg                             \
_(DNS_ENABLE_DISABLE, dns_enable_disable)               \
_(DNS_NAME_SERVER_ADD_DEL, dns_name_server_add_del)     \
_(DNS_RESOLVE_NAME, dns_resolve_name)			\
_(DNS_RESOLVE_IP, dns_resolve_ip)

static clib_error_t *
dns_api_hookup (vlib_main_t * vm)
{
#define _(N,n)                                                  \
    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
                           vl_api_##n##_t_handler,              \
                           vl_noop_handler,                     \
                           vl_api_##n##_t_endian,               \
                           vl_api_##n##_t_print,                \
                           sizeof(vl_api_##n##_t), 1);
  foreach_dns_api_msg;
#undef _

  setup_message_id_table (&api_main);
  return 0;
}

VLIB_API_INIT_FUNCTION (dns_api_hookup);


static clib_error_t *
dns_config_fn (vlib_main_t * vm, unformat_input_t * input)
{
  dns_main_t *dm = &dns_main;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "max-cache-size %u", &dm->name_cache_size))
	;
      else if (unformat (input, "max-ttl %u", &dm->max_ttl_in_seconds))
	;
      else
	return clib_error_return (0, "unknown input `%U'",
				  format_unformat_error, input);
    }
  return 0;
}

VLIB_CONFIG_FUNCTION (dns_config_fn, "dns");

static clib_error_t *
dns_init (vlib_main_t * vm)
{
  dns_main_t *dm = &dns_main;

  dm->vlib_main = vm;
  dm->vnet_main = vnet_get_main ();
  dm->name_cache_size = 65535;
  dm->max_ttl_in_seconds = 86400;
  dm->random_seed = 0xDEADDABE;

  udp_register_dst_port (vm, UDP_DST_PORT_dns_reply, dns46_reply_node.index,
			 1 /* is_ip4 */ );

  udp_register_dst_port (vm, UDP_DST_PORT_dns_reply6, dns46_reply_node.index,
			 0 /* is_ip4 */ );

  udp_register_dst_port (vm, UDP_DST_PORT_dns, dns4_request_node.index,
			 1 /* is_ip4 */ );

  udp_register_dst_port (vm, UDP_DST_PORT_dns6, dns6_request_node.index,
			 0 /* is_ip4 */ );
  return 0;
}

VLIB_INIT_FUNCTION (dns_init);

uword
unformat_dns_reply (unformat_input_t * input, va_list * args)
{
  u8 **result = va_arg (*args, u8 **);
  u8 **namep = va_arg (*args, u8 **);
  ip4_address_t a4;
  ip6_address_t a6;
  int a4_set = 0;
  int a6_set = 0;
  u8 *name;
  int name_set = 0;
  u8 *ce;
  u32 qp_offset;
  dns_header_t *h;
  dns_query_t *qp;
  dns_rr_t *rr;
  u8 *rru8;

  if (unformat (input, "%v", &name))
    name_set = 1;

  if (unformat (input, "%U", unformat_ip4_address, &a4))
    {
      a4_set = 1;
      if (unformat (input, "%U", unformat_ip6_address, &a6))
	a6_set = 1;
    }

  if (unformat (input, "%U", unformat_ip6_address, &a6))
    {
      a6_set = 1;
      if (unformat (input, "%U", unformat_ip4_address, &a6))
	a4_set = 1;
    }

  /* Must have a name */
  if (!name_set)
    return 0;

  /* Must have at least one address */
  if (!(a4_set + a6_set))
    return 0;

  /* Build a fake DNS cache entry string, one hemorrhoid at a time */
  ce = name_to_labels (name);
  qp_offset = vec_len (ce);

  /* Add space for the query header */
  vec_validate (ce, qp_offset + sizeof (dns_query_t) - 1);
  qp = (dns_query_t *) (ce + qp_offset);

  qp->type = clib_host_to_net_u16 (DNS_TYPE_ALL);
  qp->class = clib_host_to_net_u16 (DNS_CLASS_IN);

  /* Punch in space for the dns_header_t */
  vec_insert (ce, sizeof (dns_header_t), 0);

  h = (dns_header_t *) ce;

  /* Fake Transaction ID */
  h->id = 0xFFFF;

  h->flags = clib_host_to_net_u16 (DNS_RD | DNS_RA);
  h->qdcount = clib_host_to_net_u16 (1);
  h->anscount = clib_host_to_net_u16 (a4_set + a6_set);
  h->nscount = 0;
  h->arcount = 0;

  /* Now append one or two A/AAAA RR's... */
  if (a4_set)
    {
      /* Pointer to the name (DGMS) */
      vec_add1 (ce, 0xC0);
      vec_add1 (ce, 0x0C);
      vec_add2 (ce, rru8, sizeof (*rr) + 4);
      rr = (void *) rru8;
      rr->type = clib_host_to_net_u16 (DNS_TYPE_A);
      rr->class = clib_host_to_net_u16 (DNS_CLASS_IN);
      rr->ttl = clib_host_to_net_u32 (86400);
      rr->rdlength = clib_host_to_net_u16 (4);
      memcpy (rr->rdata, &a4, sizeof (a4));
    }
  if (a6_set)
    {
      /* Pointer to the name (DGMS) */
      vec_add1 (ce, 0xC0);
      vec_add1 (ce, 0x0C);
      vec_add2 (ce, rru8, sizeof (*rr) + 16);
      rr = (void *) rru8;
      rr->type = clib_host_to_net_u16 (DNS_TYPE_AAAA);
      rr->class = clib_host_to_net_u16 (DNS_CLASS_IN);
      rr->ttl = clib_host_to_net_u32 (86400);
      rr->rdlength = clib_host_to_net_u16 (16);
      memcpy (rr->rdata, &a6, sizeof (a6));
    }
  *result = ce;
  if (namep)
    *namep = name;
  else
    vec_free (name);

  return 1;
}

u8 *
format_dns_query (u8 * s, va_list * args)
{
  u8 **curpos = va_arg (*args, u8 **);
  int verbose = va_arg (*args, int);
  u8 *pos;
  dns_query_t *qp;
  int len, i;
  if (verbose > 1)
    s = format (s, "    Name: ");

  /* Unwind execrated counted-label sheit */
  pos = *curpos;
  len = *pos++;

  while (len)
    {
      for (i = 0; i < len; i++)
	vec_add1 (s, *pos++);

      len = *pos++;
      if (len)
	vec_add1 (s, '.');
      else
	{
	  vec_add1 (s, ':');
	  vec_add1 (s, ' ');
	}
    }

  qp = (dns_query_t *) pos;
  if (verbose > 1)
    {
      switch (clib_net_to_host_u16 (qp->type))
	{
	case DNS_TYPE_A:
	  s = format (s, "type A\n");
	  break;
	case DNS_TYPE_AAAA:
	  s = format (s, "type AAAA\n");
	  break;
	case DNS_TYPE_ALL:
	  s = format (s, "type ALL\n");
	  break;

	default:
	  s = format (s, "type %d\n", clib_net_to_host_u16 (qp->type));
	  break;
	}
    }

  pos += sizeof (*qp);

  *curpos = pos;
  return s;
}

/**
 * format dns reply data
 * verbose > 1, dump everything
 * verbose == 1, dump all A and AAAA records
 * verbose == 0, dump one A record, and one AAAA record
 */

u8 *
format_dns_reply_data (u8 * s, va_list * args)
{
  u8 *reply = va_arg (*args, u8 *);
  u8 **curpos = va_arg (*args, u8 **);
  int verbose = va_arg (*args, int);
  int *print_ip4 = va_arg (*args, int *);
  int *print_ip6 = va_arg (*args, int *);
  int len;
  u8 *pos, *pos2;
  dns_rr_t *rr;
  int i;
  int pointer_chase = 0;
  u16 *tp;
  u16 rrtype_host_byte_order;

  pos = pos2 = *curpos;

  if (verbose > 1)
    s = format (s, "    ");

  /* chase pointer? almost always yes here... */
  if ((pos2[0] & 0xc0) == 0xc0)
    {
      pos = pos2 + 2;
      pos2 = reply + ((pos2[0] & 0x3f) << 8) + pos2[1];
      pointer_chase = 1;
    }

  len = *pos2++;

  while (len)
    {
      for (i = 0; i < len; i++)
	{
	  if (verbose > 1)
	    vec_add1 (s, *pos2);
	  pos2++;
	}
      if ((pos2[0] & 0xc0) == 0xc0)
	{
	  /*
	   * If we've already done one pointer chase,
	   * do not move the pos pointer.
	   */
	  if (pointer_chase == 0)
	    pos = pos2 + 2;
	  pos2 = reply + ((pos2[0] & 0x3f) << 8) + pos2[1];
	  len = *pos2++;
	  pointer_chase = 1;
	}
      else
	len = *pos2++;
      if (len)
	{
	  if (verbose > 1)
	    vec_add1 (s, '.');
	}
      else
	{
	  if (verbose > 1)
	    vec_add1 (s, ' ');
	}
    }

  if (pointer_chase == 0)
    pos = pos2;

  rr = (dns_rr_t *) pos;
  rrtype_host_byte_order = clib_net_to_host_u16 (rr->type);

  switch (rrtype_host_byte_order)
    {
    case DNS_TYPE_A:
      if (verbose > 1)
	{
	  s = format (s, "A: ttl %d %U\n", clib_net_to_host_u32 (rr->ttl),
		      format_ip4_address, rr->rdata);
	}
      else
	{
	  if (*print_ip4)
	    s = format (s, "%U [%u] ", format_ip4_address, rr->rdata,
			clib_net_to_host_u32 (rr->ttl));
	  if (verbose == 0)
	    *print_ip4 = 0;

	}
      pos += sizeof (*rr) + 4;
      break;

    case DNS_TYPE_AAAA:
      if (verbose > 1)
	{
	  s = format (s, "AAAA: ttl %d %U\n", clib_net_to_host_u32 (rr->ttl),
		      format_ip6_address, rr->rdata);
	}
      else
	{
	  if (*print_ip6)
	    s = format (s, "%U [%u] ", format_ip6_address, rr->rdata,
			clib_net_to_host_u32 (rr->ttl));
	  if (verbose == 0)
	    *print_ip6 = 0;
	}
      pos += sizeof (*rr) + 16;
      break;

    case DNS_TYPE_TEXT:
      if (verbose > 1)
	{
	  s = format (s, "TEXT: ");
	  for (i = 0; i < clib_net_to_host_u16 (rr->rdlength); i++)
	    vec_add1 (s, rr->rdata[i]);
	  vec_add1 (s, '\n');
	}
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;

    case DNS_TYPE_HINFO:
      {
	/* Two counted strings. DGMS */
	u8 *len;
	u8 *curpos;
	int i;
	if (verbose > 1)
	  {
	    s = format (s, "HINFO: ");
	    len = rr->rdata;
	    curpos = len + 1;
	    for (i = 0; i < *len; i++)
	      vec_add1 (s, *curpos++);

	    vec_add1 (s, ' ');
	    len = curpos++;
	    for (i = 0; i < *len; i++)
	      vec_add1 (s, *curpos++);

	    vec_add1 (s, '\n');
	  }
      }
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;

    case DNS_TYPE_NAMESERVER:
      if (verbose > 1)
	{
	  s = format (s, "Nameserver: ");
	  pos2 = rr->rdata;

	  /* chase pointer? */
	  if ((pos2[0] & 0xc0) == 0xc0)
	    {
	      pos = pos2 + 2;
	      pos2 = reply + ((pos2[0] & 0x3f) << 8) + pos2[1];
	    }

	  len = *pos2++;

	  while (len)
	    {
	      for (i = 0; i < len; i++)
		vec_add1 (s, *pos2++);

	      /* chase pointer, typically to offset 12... */
	      if (pos2[0] == 0xC0)
		pos2 = reply + pos2[1];

	      len = *pos2++;
	      if (len)
		vec_add1 (s, '.');
	      else
		vec_add1 (s, '\n');
	    }
	}
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;

    case DNS_TYPE_MAIL_EXCHANGE:
      if (verbose > 1)
	{
	  tp = (u16 *) rr->rdata;

	  s = format (s, "Mail Exchange: Preference %d ", (u32)
		      clib_net_to_host_u16 (*tp));

	  pos2 = rr->rdata + 2;

	  /* chase pointer? */
	  if (pos2[0] == 0xc0)
	    pos2 = reply + pos2[1];

	  len = *pos2++;

	  while (len)
	    {
	      for (i = 0; i < len; i++)
		vec_add1 (s, *pos2++);

	      /* chase pointer */
	      if (pos2[0] == 0xC0)
		pos2 = reply + pos2[1];

	      len = *pos2++;
	      if (len)
		vec_add1 (s, '.');
	      else
		vec_add1 (s, '\n');
	    }
	}

      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;

    case DNS_TYPE_PTR:
    case DNS_TYPE_CNAME:
      if (verbose > 1)
	{
	  tp = (u16 *) rr->rdata;

	  if (rrtype_host_byte_order == DNS_TYPE_CNAME)
	    s = format (s, "CNAME: ");
	  else
	    s = format (s, "PTR: ");

	  pos2 = rr->rdata;

	  /* chase pointer? */
	  if (pos2[0] == 0xc0)
	    pos2 = reply + pos2[1];

	  len = *pos2++;

	  while (len)
	    {
	      for (i = 0; i < len; i++)
		vec_add1 (s, *pos2++);

	      /* chase pointer */
	      if (pos2[0] == 0xC0)
		pos2 = reply + pos2[1];

	      len = *pos2++;
	      if (len)
		vec_add1 (s, '.');
	      else
		vec_add1 (s, '\n');
	    }
	}
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;

    default:
      if (verbose > 1)
	s = format (s, "type %d: len %d\n",
		    (int) clib_net_to_host_u16 (rr->type),
		    sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength));
      pos += sizeof (*rr) + clib_net_to_host_u16 (rr->rdlength);
      break;
    }

  *curpos = pos;

  return s;
}

u8 *
format_dns_reply (u8 * s, va_list * args)
{
  u8 *reply_as_u8 = va_arg (*args, u8 *);
  int verbose = va_arg (*args, int);
  dns_header_t *h;
  u16 id, flags;
  u8 *curpos;
  int i;
  int print_ip4 = 1;
  int print_ip6 = 1;

  h = (dns_header_t *) reply_as_u8;
  id = clib_net_to_host_u16 (h->id);
  flags = clib_net_to_host_u16 (h->flags);

  if (verbose > 1)
    {
      s = format (s, "DNS %s: id %d\n", (flags & DNS_QR) ? "reply" : "query",
		  id);
      s = format (s, "  %s %s %s %s\n",
		  (flags & DNS_RA) ? "recur" : "no-recur",
		  (flags & DNS_RD) ? "recur-des" : "no-recur-des",
		  (flags & DNS_TC) ? "trunc" : "no-trunc",
		  (flags & DNS_AA) ? "auth" : "non-auth");
      s = format (s, "  %d queries, %d answers, %d name-servers,"
		  " %d add'l recs\n",
		  clib_net_to_host_u16 (h->qdcount),
		  clib_net_to_host_u16 (h->anscount),
		  clib_net_to_host_u16 (h->nscount),
		  clib_net_to_host_u16 (h->arcount));
    }

  curpos = (u8 *) (h + 1);

  if (h->qdcount)
    {
      if (verbose > 1)
	s = format (s, "  Queries:\n");
      for (i = 0; i < clib_net_to_host_u16 (h->qdcount); i++)
	{
	  /* The query is variable-length, so curpos is a value-result parm */
	  s = format (s, "%U", format_dns_query, &curpos, verbose);
	}
    }
  if (h->anscount)
    {
      if (verbose > 1)
	s = format (s, "  Replies:\n");

      for (i = 0; i < clib_net_to_host_u16 (h->anscount); i++)
	{
	  /* curpos is a value-result parm */
	  s = format (s, "%U", format_dns_reply_data, reply_as_u8, &curpos,
		      verbose, &print_ip4, &print_ip6);
	}
    }
  return s;
}

u8 *
format_dns_cache (u8 * s, va_list * args)
{
  dns_main_t *dm = va_arg (*args, dns_main_t *);
  f64 now = va_arg (*args, f64);
  int verbose = va_arg (*args, int);
  u8 *name = va_arg (*args, u8 *);
  dns_cache_entry_t *ep;
  char *ss;
  uword *p;

  if (dm->is_enabled == 0)
    {
      s = format (s, "The DNS cache is disabled...");
      return s;
    }

  if (pool_elts (dm->entries) == 0)
    {
      s = format (s, "The DNS cache is empty...");
      return s;
    }

  dns_cache_lock (dm);

  if (name)
    {
      p = hash_get_mem (dm->cache_entry_by_name, name);
      if (!p)
	{
	  s = format (s, "%s is not in the cache...", name);
	  dns_cache_unlock (dm);
	  return (s);
	}

      ep = pool_elt_at_index (dm->entries, p[0]);
      /* Magic to spit out a C-initializer to research hemorrhoids... */
      if (verbose == 3)
	{
	  int i, j;
	  s = format (s, "static u8 dns_reply_data_initializer[] =\n");
	  s = format (s, "{\n");
	  j = 0;
	  for (i = 0; i < vec_len (ep->dns_response); i++)
	    {
	      if (j++ == 8)
		{
		  j = 0;
		  vec_add1 (s, '\n');
		}
	      s = format (s, "0x%02x, ", ep->dns_response[i]);
	    }
	  s = format (s, "};\n");
	}
      else
	{
	  if (ep->flags & DNS_CACHE_ENTRY_FLAG_VALID)
	    {
	      ASSERT (ep->dns_response);
	      if (ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC)
		ss = "[S] ";
	      else
		ss = "    ";

	      if (verbose < 2 && ep->flags & DNS_CACHE_ENTRY_FLAG_CNAME)
		s = format (s, "%s%s -> %s", ss, ep->name, ep->cname);
	      else
		s = format (s, "%s%s -> %U", ss, ep->name,
			    format_dns_reply, ep->dns_response, verbose);
	      if (!(ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC))
		{
		  f64 time_left = ep->expiration_time - now;
		  if (time_left > 0.0)
		    s = format (s, "  TTL left %.1f", time_left);
		  else
		    s = format (s, "  EXPIRED");
		}
	    }
	  else
	    {
	      ASSERT (ep->dns_request);
	      s = format (s, "[P] %U", format_dns_reply, ep->dns_request,
			  verbose);
	    }
	  vec_add1 (s, '\n');
	}
      return s;
    }

  /* *INDENT-OFF* */
  pool_foreach (ep, dm->entries,
  ({
    if (ep->flags & DNS_CACHE_ENTRY_FLAG_VALID)
      {
        ASSERT (ep->dns_response);
        if (ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC)
          ss = "[S] ";
        else
          ss = "    ";

        if (verbose < 2 && ep->flags & DNS_CACHE_ENTRY_FLAG_CNAME)
          s = format (s, "%s%s -> %s", ss, ep->name, ep->cname);
        else
          s = format (s, "%s%s -> %U", ss, ep->name,
                      format_dns_reply,
                      ep->dns_response,
                      verbose);
        if (!(ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC))
          {
            f64 time_left = ep->expiration_time - now;
            if (time_left > 0.0)
              s = format (s, "  TTL left %.1f", time_left);
            else
              s = format (s, "  EXPIRED");

            if (verbose > 2)
              s = format (s, "    %d client notifications pending\n",
                          vec_len(ep->pending_requests));
          }
      }
    else
      {
        ASSERT (ep->dns_request);
        s = format (s, "[P] %U", format_dns_reply, ep->dns_request,
                    verbose);
      }
    vec_add1 (s, '\n');
  }));
  /* *INDENT-ON* */

  dns_cache_unlock (dm);

  return s;
}

static clib_error_t *
show_dns_cache_command_fn (vlib_main_t * vm,
			   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  dns_main_t *dm = &dns_main;
  int verbose = 0;
  u8 *name = 0;
  f64 now = vlib_time_now (vm);

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "verbose %d", &verbose))
	;
      else if (unformat (input, "verbose"))
	verbose = 1;
      else if (unformat (input, "name %s", &name))
	;
      else
	return clib_error_return (0, "unknown input `%U'",
				  format_unformat_error, input);
    }

  vlib_cli_output (vm, "%U", format_dns_cache, dm, now, verbose, name);

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_dns_cache_command) =
{
  .path = "show dns cache",
  .short_help = "show dns cache [verbose [nn]]",
  .function = show_dns_cache_command_fn,
};
/* *INDENT-ON* */

static clib_error_t *
dns_cache_add_del_command_fn (vlib_main_t * vm,
			      unformat_input_t * input,
			      vlib_cli_command_t * cmd)
{
  dns_main_t *dm = &dns_main;
  u8 *dns_reply_data;
  u8 *name;
  int is_add = -1;
  int is_clear = -1;
  int rv;
  clib_error_t *error;

  if (unformat (input, "add"))
    is_add = 1;
  if (unformat (input, "del"))
    is_add = 0;
  if (unformat (input, "clear"))
    is_clear = 1;

  if (is_add == -1 && is_clear == -1)
    return clib_error_return (0, "add / del / clear required...");

  if (is_clear == 1)
    {
      rv = dns_cache_clear (dm);
      switch (rv)
	{
	case 0:
	  return 0;

	case VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED:
	  error = clib_error_return (0, "Name resolution not enabled");
	  return error;
	}
    }

  /* Delete (by name)? */
  if (is_add == 0)
    {
      if (unformat (input, "%v", &name))
	{
	  rv = dns_delete_by_name (dm, name);
	  switch (rv)
	    {
	    case VNET_API_ERROR_NO_SUCH_ENTRY:
	      error = clib_error_return (0, "%v not in the cache...", name);
	      vec_free (name);
	      return error;

	    case VNET_API_ERROR_NAME_RESOLUTION_NOT_ENABLED:
	      error = clib_error_return (0, "Name resolution not enabled");
	      vec_free (name);
	      return error;

	    case 0:
	      vec_free (name);
	      return 0;

	    default:
	      error = clib_error_return (0, "dns_delete_by_name returned %d",
					 rv);
	      vec_free (name);
	      return error;
	    }
	}
      return clib_error_return (0, "unknown input `%U'",
				format_unformat_error, input);
    }

  /* Note: dns_add_static_entry consumes the name vector if OK... */
  if (unformat (input, "%U", unformat_dns_reply, &dns_reply_data, &name))
    {
      rv = dns_add_static_entry (dm, name, dns_reply_data);
      switch (rv)
	{
	case VNET_API_ERROR_ENTRY_ALREADY_EXISTS:
	  vec_free (name);
	  vec_free (dns_reply_data);
	  return clib_error_return (0, "%v already in the cache...", name);
	case 0:
	  return 0;

	default:
	  return clib_error_return (0, "dns_add_static_entry returned %d",
				    rv);
	}
    }

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (dns_cache_add_del_command) =
{
  .path = "dns cache",
  .short_help = "dns cache [add|del|clear] <name> [ip4][ip6]",
  .function = dns_cache_add_del_command_fn,
};
/* *INDENT-ON* */

#define DNS_FORMAT_TEST 0

#if DNS_FORMAT_TEST > 0
#if 0
/* yahoo.com */
static u8 dns_reply_data_initializer[] =
  { 0x0, 0x0, 0x81, 0x80, 0x0, 0x1, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x5,
  0x79, 0x61, 0x68, 0x6f, 0x6f, 0x3, 0x63, 0x6f, 0x6d,
  0x0,				/* null lbl */
  0x0, 0xff,			/* type ALL */
  0x0, 0x1,			/* class IN */
  0xc0, 0xc,			/* pointer to yahoo.com name */
  0x0, 0x10, 0x0, 0x1, 0x0, 0x0, 0x6, 0x5c, 0x0, 0x24, 0x23,
  0x76, 0x3d, 0x73, 0x70, 0x66, 0x31, 0x20, 0x72, 0x65, 0x64, 0x69, 0x72,
  0x65, 0x63, 0x74, 0x3d, 0x5f, 0x73, 0x70, 0x66, 0x2e, 0x6d, 0x61, 0x69,
  0x6c, 0x2e, 0x79, 0x61, 0x68, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0xc0,
  0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x50, 0xd4, 0x0, 0x6, 0x3, 0x6e, 0x73,
  0x35, 0xc0, 0xc, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x50, 0xd4, 0x0,
  0x6, 0x3, 0x6e, 0x73, 0x34, 0xc0, 0xc, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0,
  0x1, 0x50, 0xd4, 0x0, 0x6, 0x3, 0x6e, 0x73, 0x31, 0xc0, 0xc, 0xc0, 0xc,
  0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x50, 0xd4, 0x0, 0x6, 0x3, 0x6e, 0x73, 0x32,
  0xc0, 0xc, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x50, 0xd4, 0x0, 0x6,
  0x3, 0x6e, 0x73, 0x33, 0xc0, 0xc, 0xc0, 0xc, 0x0, 0xf, 0x0, 0x1, 0x0, 0x0,
  0x6, 0x5c, 0x0, 0x19, 0x0, 0x1, 0x4, 0x6d, 0x74, 0x61, 0x36, 0x3, 0x61,
  0x6d, 0x30, 0x8, 0x79, 0x61, 0x68, 0x6f, 0x6f, 0x64, 0x6e, 0x73, 0x3,
  0x6e,
  0x65, 0x74, 0x0, 0xc0, 0xc, 0x0, 0xf, 0x0, 0x1, 0x0, 0x0, 0x6, 0x5c, 0x0,
  0x9, 0x0, 0x1, 0x4, 0x6d, 0x74, 0x61, 0x37, 0xc0, 0xb8, 0xc0, 0xc, 0x0,
  0xf, 0x0, 0x1, 0x0, 0x0, 0x6, 0x5c, 0x0, 0x9, 0x0, 0x1, 0x4, 0x6d, 0x74,
  0x61, 0x35, 0xc0, 0xb8, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0, 0x0, 0x6,
  0x5c, 0x0, 0x10, 0x20, 0x1, 0x49, 0x98, 0x0, 0x44, 0x2, 0x4, 0x0, 0x0,
  0x0,
  0x0, 0x0, 0x0, 0x0, 0xa7, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0, 0x0, 0x6,
  0x5c, 0x0, 0x10, 0x20, 0x1, 0x49, 0x98, 0x0, 0xc, 0xa, 0x6, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x2, 0x40, 0x8, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0, 0x0, 0x6,
  0x5c, 0x0, 0x10, 0x20, 0x1, 0x49, 0x98, 0x0, 0x58, 0xc, 0x2, 0x0, 0x0,
  0x0,
  0x0, 0x0, 0x0, 0x0, 0xa9, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x6,
  0x5c, 0x0, 0x4, 0x62, 0x8a, 0xfd, 0x6d, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1,
  0x0,
  0x0, 0x6, 0x5c, 0x0, 0x4, 0xce, 0xbe, 0x24, 0x2d, 0xc0, 0xc, 0x0, 0x1,
  0x0,
  0x1, 0x0, 0x0, 0x6, 0x5c, 0x0, 0x4, 0x62, 0x8b, 0xb4, 0x95, 0xc0, 0xc,
  0x0,
  0x6, 0x0, 0x1, 0x0, 0x0, 0x6, 0x5c, 0x0, 0x2d, 0xc0, 0x7b, 0xa, 0x68,
  0x6f,
  0x73, 0x74, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x9, 0x79, 0x61, 0x68,
  0x6f, 0x6f, 0x2d, 0x69, 0x6e, 0x63, 0xc0, 0x12, 0x78, 0x3a, 0x85, 0x44,
  0x0, 0x0, 0xe, 0x10, 0x0, 0x0, 0x1, 0x2c, 0x0, 0x1b, 0xaf, 0x80, 0x0, 0x0,
  0x2, 0x58
};

/* www.cisco.com, has no addresses in reply */
static u8 dns_reply_data_initializer[] = {
  0x00, 0x01, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x05,
  0x63, 0x69, 0x73, 0x63, 0x6f, 0x03, 0x63, 0x6f, 0x6d,

  0x00, 0x00, 0xff, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05,
  0x00, 0x01, 0x00, 0x00, 0x0b, 0xd3, 0x00, 0x1a, 0x03,
  0x77, 0x77, 0x77, 0x05, 0x63, 0x69, 0x73, 0x63, 0x6f,
  0x03, 0x63, 0x6f, 0x6d, 0x06, 0x61, 0x6b, 0x61, 0x64,
  0x6e, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00,
};

/* bind8 (linux widget, w/ nasty double pointer chasees */
static u8 dns_reply_data_initializer[] = {
  /* 0 */
  0x00, 0x01, 0x81, 0x80, 0x00, 0x01, 0x00, 0x08,
  /* 8 */
  0x00, 0x06, 0x00, 0x06, 0x0a, 0x6f, 0x72, 0x69,
  /* 16 */
  0x67, 0x69, 0x6e, 0x2d, 0x77, 0x77, 0x77, 0x05,
  /* 24 */
  0x63, 0x69, 0x73, 0x63, 0x6f, 0x03, 0x63, 0x6f,
  /* 32 */
  0x6d, 0x00, 0x00, 0xff, 0x00, 0x01, 0x0a, 0x6f,
  /* 40 */
  0x72, 0x69, 0x67, 0x69, 0x6e, 0x2d, 0x77, 0x77,
  /* 48 */
  0x77, 0x05, 0x43, 0x49, 0x53, 0x43, 0x4f, 0xc0,

  /* 56 */
  0x1d, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x05,

  /* 64 */
  0x9a, 0x00, 0x18, 0x15, 0x72, 0x63, 0x64,
  0x6e, 0x39, 0x2d, 0x31, 0x34, 0x70, 0x2d, 0x64, 0x63,
  0x7a, 0x30, 0x35, 0x6e, 0x2d, 0x67, 0x73, 0x73, 0x31,
  0xc0, 0x17, 0xc0, 0x26, 0x00, 0x02, 0x00, 0x01, 0x00,
  0x00, 0x05, 0x9a, 0x00, 0x1a, 0x17, 0x61, 0x6c, 0x6c,
  0x6e, 0x30, 0x31, 0x2d, 0x61, 0x67, 0x30, 0x39, 0x2d,
  0x64, 0x63, 0x7a, 0x30, 0x33, 0x6e, 0x2d, 0x67, 0x73,
  0x73, 0x31, 0xc0, 0x17, 0xc0, 0x26, 0x00, 0x02, 0x00,
  0x01, 0x00, 0x00, 0x05, 0x9a, 0x00, 0x10, 0x0d, 0x72,
  0x74, 0x70, 0x35, 0x2d, 0x64, 0x6d, 0x7a, 0x2d, 0x67,
  0x73, 0x73, 0x31, 0xc0, 0x17, 0xc0, 0x26, 0x00, 0x02,
  0x00, 0x01, 0x00, 0x00, 0x05, 0x9a, 0x00, 0x18, 0x15,
  0x6d, 0x74, 0x76, 0x35, 0x2d, 0x61, 0x70, 0x31, 0x30,
  0x2d, 0x64, 0x63, 0x7a, 0x30, 0x36, 0x6e, 0x2d, 0x67,
  0x73, 0x73, 0x31, 0xc0, 0x17, 0xc0, 0x26, 0x00, 0x02,
  0x00, 0x01, 0x00, 0x00, 0x05, 0x9a, 0x00, 0x1b, 0x18,
  0x73, 0x6e, 0x67, 0x64, 0x63, 0x30, 0x31, 0x2d, 0x61,
  0x62, 0x30, 0x37, 0x2d, 0x64, 0x63, 0x7a, 0x30, 0x31,
  0x6e, 0x2d, 0x67, 0x73, 0x73, 0x31, 0xc0, 0x17, 0xc0,
  0x26, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x05, 0x9a,
  0x00, 0x1a, 0x17, 0x61, 0x65, 0x72, 0x30, 0x31, 0x2d,
  0x72, 0x34, 0x63, 0x32, 0x35, 0x2d, 0x64, 0x63, 0x7a,
  0x30, 0x31, 0x6e, 0x2d, 0x67, 0x73, 0x73, 0x31, 0xc0,
  0x17, 0xc0, 0x26, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
  0x00, 0x81, 0x00, 0x04, 0x48, 0xa3, 0x04, 0xa1, 0xc0,
  0x26, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82,
  0x00, 0x10, 0x20, 0x01, 0x04, 0x20, 0x12, 0x01, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
  0xc0, 0x0c, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x05,
  0x9a, 0x00, 0x02, 0xc0, 0xf4, 0xc0, 0x0c, 0x00, 0x02,
  0x00, 0x01, 0x00, 0x00, 0x05, 0x9a, 0x00, 0x02, 0xc0,
  0xcd, 0xc0, 0x0c, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00,
  0x05, 0x9a, 0x00, 0x02, 0xc0, 0x8d, 0xc0, 0x0c, 0x00,
  0x02, 0x00, 0x01, 0x00, 0x00, 0x05, 0x9a, 0x00, 0x02,
  0xc0, 0x43, 0xc0, 0x0c, 0x00, 0x02, 0x00, 0x01, 0x00,
  0x00, 0x05, 0x9a, 0x00, 0x02, 0xc0, 0xa9, 0xc0, 0x0c,
  0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x05, 0x9a, 0x00,
  0x02, 0xc0, 0x67, 0xc0, 0x8d, 0x00, 0x01, 0x00, 0x01,
  0x00, 0x00, 0x07, 0x08, 0x00, 0x04, 0x40, 0x66, 0xf6,
  0x05, 0xc0, 0xa9, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
  0x07, 0x08, 0x00, 0x04, 0xad, 0x24, 0xe0, 0x64, 0xc0,
  0x43, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x07, 0x08,
  0x00, 0x04, 0x48, 0xa3, 0x04, 0x1c, 0xc0, 0xf4, 0x00,
  0x01, 0x00, 0x01, 0x00, 0x00, 0x07, 0x08, 0x00, 0x04,
  0xad, 0x26, 0xd4, 0x6c, 0xc0, 0x67, 0x00, 0x01, 0x00,
  0x01, 0x00, 0x00, 0x07, 0x08, 0x00, 0x04, 0xad, 0x25,
  0x90, 0x64, 0xc0, 0xcd, 0x00, 0x01, 0x00, 0x01, 0x00,
  0x00, 0x07, 0x08, 0x00, 0x04, 0xad, 0x27, 0x70, 0x44,
};

/* google.com */
#else
static u8 dns_reply_data_initializer[] =
  { 0x0, 0x0, 0x81, 0x80, 0x0, 0x1, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x6,
  0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x3, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0xff,
  0x0, 0x1, 0xc0, 0xc, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x1, 0x2b, 0x0, 0x4,
  0xac, 0xd9, 0x3, 0x2e, 0xc0, 0xc, 0x0, 0x1c, 0x0, 0x1, 0x0, 0x0, 0x1,
  0x2b,
  0x0, 0x10, 0x26, 0x7, 0xf8, 0xb0, 0x40, 0x4, 0x8, 0xf, 0x0, 0x0, 0x0, 0x0,
  0x0, 0x0, 0x20, 0xe, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x51, 0x7f,
  0x0, 0x6, 0x3, 0x6e, 0x73, 0x31, 0xc0, 0xc, 0xc0, 0xc, 0x0, 0x6, 0x0, 0x1,
  0x0, 0x0, 0x0, 0x3b, 0x0, 0x22, 0xc0, 0x54, 0x9, 0x64, 0x6e, 0x73, 0x2d,
  0x61, 0x64, 0x6d, 0x69, 0x6e, 0xc0, 0xc, 0xa, 0x3d, 0xc7, 0x30, 0x0, 0x0,
  0x3, 0x84, 0x0, 0x0, 0x3, 0x84, 0x0, 0x0, 0x7, 0x8, 0x0, 0x0, 0x0, 0x3c,
  0xc0, 0xc, 0x0, 0xf, 0x0, 0x1, 0x0, 0x0, 0x2, 0x57, 0x0, 0x11, 0x0, 0x1e,
  0x4, 0x61, 0x6c, 0x74, 0x32, 0x5, 0x61, 0x73, 0x70, 0x6d, 0x78, 0x1, 0x6c,
  0xc0, 0xc, 0xc0, 0xc, 0x0, 0xf, 0x0, 0x1, 0x0, 0x0, 0x2, 0x57, 0x0, 0x4,
  0x0, 0xa, 0xc0, 0x9b, 0xc0, 0xc, 0x0, 0x10, 0x0, 0x1, 0x0, 0x0, 0xe, 0xf,
  0x0, 0x24, 0x23, 0x76, 0x3d, 0x73, 0x70, 0x66, 0x31, 0x20, 0x69, 0x6e,
  0x63, 0x6c, 0x75, 0x64, 0x65, 0x3a, 0x5f, 0x73, 0x70, 0x66, 0x2e, 0x67,
  0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x7e, 0x61,
  0x6c, 0x6c, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1, 0x51, 0x7f, 0x0, 0x6,
  0x3, 0x6e, 0x73, 0x32, 0xc0, 0xc, 0xc0, 0xc, 0x1, 0x1, 0x0, 0x1, 0x0, 0x1,
  0x51, 0x7f, 0x0, 0xf, 0x0, 0x5, 0x69, 0x73, 0x73, 0x75, 0x65, 0x70, 0x6b,
  0x69, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0,
  0x1, 0x51, 0x7f, 0x0, 0x6, 0x3, 0x6e, 0x73, 0x34, 0xc0, 0xc, 0xc0, 0xc,
  0x0, 0xf, 0x0, 0x1, 0x0, 0x0, 0x2, 0x57, 0x0, 0x9, 0x0, 0x28, 0x4, 0x61,
  0x6c, 0x74, 0x33, 0xc0, 0x9b, 0xc0, 0xc, 0x0, 0x2, 0x0, 0x1, 0x0, 0x1,
  0x51, 0x7f, 0x0, 0x6, 0x3, 0x6e, 0x73, 0x33, 0xc0, 0xc, 0xc0, 0xc, 0x0,
  0xf, 0x0, 0x1, 0x0, 0x0, 0x2, 0x57, 0x0, 0x9, 0x0, 0x32, 0x4, 0x61, 0x6c,
  0x74, 0x34, 0xc0, 0x9b, 0xc0, 0xc, 0x0, 0xf, 0x0, 0x1, 0x0, 0x0, 0x2,
  0x57,
  0x0, 0x9, 0x0, 0x14, 0x4, 0x61, 0x6c, 0x74, 0x31, 0xc0, 0x9b
};
#endif

static clib_error_t *
test_dns_fmt_command_fn (vlib_main_t * vm,
			 unformat_input_t * input, vlib_cli_command_t * cmd)
{
  u8 *dns_reply_data = 0;
  int verbose = 0;
  int rv;
  vl_api_dns_resolve_name_reply_t _rm, *rmp = &_rm;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "verbose %d", &verbose))
	;
      else if (unformat (input, "verbose"))
	verbose = 1;
      else
	return clib_error_return (0, "unknown input `%U'",
				  format_unformat_error, input);
    }

  vec_validate (dns_reply_data, ARRAY_LEN (dns_reply_data_initializer) - 1);

  memcpy (dns_reply_data, dns_reply_data_initializer,
	  ARRAY_LEN (dns_reply_data_initializer));

  vlib_cli_output (vm, "%U", format_dns_reply, dns_reply_data, verbose);

  memset (rmp, 0, sizeof (*rmp));

  rv = vnet_dns_response_to_reply (dns_reply_data, rmp, 0 /* ttl-ptr */ );

  switch (rv)
    {
    case VNET_API_ERROR_NAME_SERVER_NO_ADDRESSES:
      vlib_cli_output (vm, "no addresses found...");
      break;

    default:
      vlib_cli_output (vm, "response to reply returned %d", rv);
      break;

    case 0:
      if (rmp->ip4_set)
	vlib_cli_output (vm, "ip4 address: %U", format_ip4_address,
			 (ip4_address_t *) rmp->ip4_address);
      if (rmp->ip6_set)
	vlib_cli_output (vm, "ip6 address: %U", format_ip6_address,
			 (ip6_address_t *) rmp->ip6_address);
      break;
    }

  vec_free (dns_reply_data);

  return 0;
}


/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_dns_fmt_command) =
{
  .path = "test dns format",
  .short_help = "test dns format",
  .function = test_dns_fmt_command_fn,
};
/* *INDENT-ON* */

static clib_error_t *
test_dns_unfmt_command_fn (vlib_main_t * vm,
			   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  u8 *dns_reply_data = 0;
  int verbose = 0;
  int reply_set = 0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "verbose %d", &verbose))
	;
      else if (unformat (input, "verbose"))
	verbose = 1;
      else if (unformat (input, "%U", unformat_dns_reply, &dns_reply_data))
	reply_set = 1;
      else
	return clib_error_return (0, "unknown input `%U'",
				  format_unformat_error, input);
    }

  if (reply_set == 0)
    return clib_error_return (0, "dns data not set...");

  vlib_cli_output (vm, "%U", format_dns_reply, dns_reply_data, verbose);

  vec_free (dns_reply_data);

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_dns_unfmt_command) =
{
  .path = "test dns unformat",
  .short_help = "test dns unformat <name> [ip4][ip6]",
  .function = test_dns_unfmt_command_fn,
};
/* *INDENT-ON* */

static clib_error_t *
test_dns_expire_command_fn (vlib_main_t * vm,
			    unformat_input_t * input,
			    vlib_cli_command_t * cmd)
{
  dns_main_t *dm = &dns_main;
  u8 *name;
  uword *p;
  clib_error_t *e;
  dns_cache_entry_t *ep;

  if (unformat (input, "%v", &name))
    {
      vec_add1 (name, 0);
      _vec_len (name) -= 1;
    }

  dns_cache_lock (dm);

  p = hash_get_mem (dm->cache_entry_by_name, name);
  if (!p)
    {
      dns_cache_unlock (dm);
      e = clib_error_return (0, "%s is not in the cache...", name);
      vec_free (name);
      return e;
    }

  ep = pool_elt_at_index (dm->entries, p[0]);

  ep->expiration_time = 0;

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (test_dns_expire_command) =
{
  .path = "test dns expire",
  .short_help = "test dns expire <name>",
  .function = test_dns_expire_command_fn,
};
/* *INDENT-ON* */
#endif

void
vnet_send_dns6_reply (dns_main_t * dm, dns_pending_request_t * pr,
		      dns_cache_entry_t * ep, vlib_buffer_t * b0)
{
  clib_warning ("Unimplemented...");
}


void
vnet_send_dns4_reply (dns_main_t * dm, dns_pending_request_t * pr,
		      dns_cache_entry_t * ep, vlib_buffer_t * b0)
{
  vlib_main_t *vm = dm->vlib_main;
  u32 bi;
  fib_prefix_t prefix;
  fib_node_index_t fei;
  u32 sw_if_index, fib_index;
  ip4_main_t *im4 = &ip4_main;
  ip_lookup_main_t *lm4 = &im4->lookup_main;
  ip_interface_address_t *ia = 0;
  ip4_address_t *src_address;
  ip4_header_t *ip;
  udp_header_t *udp;
  dns_header_t *dh;
  vlib_frame_t *f;
  u32 *to_next;
  u8 *dns_response;
  u8 *reply;
  vl_api_dns_resolve_name_reply_t _rnr, *rnr = &_rnr;
  vl_api_dns_resolve_ip_reply_t _rir, *rir = &_rir;
  u32 ttl, tmp;
  u32 qp_offset;
  dns_query_t *qp;
  dns_rr_t *rr;
  u8 *rrptr;
  int is_fail = 0;

  ASSERT (ep && ep->dns_response);

  if (pr->request_type == DNS_PEER_PENDING_NAME_TO_IP)
    {
      /* Quick and dirty way to dig up the A-record address. $$ FIXME */
      memset (rnr, 0, sizeof (*rnr));
      if (vnet_dns_response_to_reply (ep->dns_response, rnr, &ttl))
	{
	  /* clib_warning ("response_to_reply failed..."); */
	  is_fail = 1;
	}
      if (rnr->ip4_set == 0)
	{
	  /* clib_warning ("No A-record..."); */
	  is_fail = 1;
	}
    }
  else if (pr->request_type == DNS_PEER_PENDING_IP_TO_NAME)
    {
      memset (rir, 0, sizeof (*rir));
      if (vnet_dns_response_to_name (ep->dns_response, rir, &ttl))
	{
	  /* clib_warning ("response_to_name failed..."); */
	  is_fail = 1;
	}
    }
  else
    {
      clib_warning ("Unknown request type %d", pr->request_type);
      return;
    }

  /* Initialize a buffer */
  if (b0 == 0)
    {
      if (vlib_buffer_alloc (vm, &bi, 1) != 1)
	return;
      b0 = vlib_get_buffer (vm, bi);
    }

  if (b0->flags & VLIB_BUFFER_NEXT_PRESENT)
    vlib_buffer_free_one (vm, b0->next_buffer);

  /*
   * Reset the buffer. We recycle the DNS request packet in the cache
   * hit case, and reply immediately from the request node.
   *
   * In the resolution-required / deferred case, resetting a freshly-allocated
   * buffer won't hurt. We hope.
   */
  b0->flags &= VLIB_BUFFER_FREE_LIST_INDEX_MASK;
  b0->flags |= (VNET_BUFFER_F_LOCALLY_ORIGINATED
		| VLIB_BUFFER_TOTAL_LENGTH_VALID);
  b0->current_data = 0;
  b0->current_length = 0;
  b0->total_length_not_including_first_buffer = 0;
  vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;	/* "local0" */
  vnet_buffer (b0)->sw_if_index[VLIB_TX] = 0;	/* default VRF for now */

  /* Find a FIB path to the peer we're trying to answer */
  clib_memcpy (&prefix.fp_addr.ip4, pr->dst_address, sizeof (ip4_address_t));
  prefix.fp_proto = FIB_PROTOCOL_IP4;
  prefix.fp_len = 32;

  fib_index = fib_table_find (prefix.fp_proto, 0 /* default VRF for now */ );
  if (fib_index == (u32) ~ 0)
    {
      clib_warning ("no fib table");
      return;
    }

  fei = fib_table_lookup (fib_index, &prefix);

  /* Couldn't find route to destination. Bail out. */
  if (fei == FIB_NODE_INDEX_INVALID)
    {
      clib_warning ("no route to DNS server");
      return;
    }

  sw_if_index = fib_entry_get_resolving_interface (fei);

  if (sw_if_index == ~0)
    {
      clib_warning
	("route to %U exists, fei %d, get_resolving_interface returned"
	 " ~0", fei, format_ip4_address, &prefix.fp_addr);
      return;
    }

  /* *INDENT-OFF* */
  foreach_ip_interface_address(lm4, ia, sw_if_index, 1 /* honor unnummbered */,
  ({
    src_address = ip_interface_address_get_address (lm4, ia);
    goto found_src_address;
  }));
  /* *INDENT-ON* */

  clib_warning ("FIB BUG");
  return;

found_src_address:

  ip = vlib_buffer_get_current (b0);
  udp = (udp_header_t *) (ip + 1);
  dns_response = (u8 *) (udp + 1);
  memset (ip, 0, sizeof (*ip) + sizeof (*udp));

  /*
   * Start with the variadic portion of the exercise.
   * Turn the name into a set of DNS "labels". Max length
   * per label is 63, enforce that.
   */
  reply = name_to_labels (pr->name);
  vec_free (pr->name);

  qp_offset = vec_len (reply);

  /* Add space for the query header */
  vec_validate (reply, qp_offset + sizeof (dns_query_t) - 1);

  qp = (dns_query_t *) (reply + qp_offset);

  if (pr->request_type == DNS_PEER_PENDING_NAME_TO_IP)
    qp->type = clib_host_to_net_u16 (DNS_TYPE_A);
  else
    qp->type = clib_host_to_net_u16 (DNS_TYPE_PTR);

  qp->class = clib_host_to_net_u16 (DNS_CLASS_IN);

  /* Punch in space for the dns_header_t */
  vec_insert (reply, sizeof (dns_header_t), 0);

  dh = (dns_header_t *) reply;

  /* Transaction ID = pool index */
  dh->id = pr->id;

  /* Announce that we did a recursive lookup */
  tmp = DNS_AA | DNS_RA | DNS_RD | DNS_OPCODE_QUERY | DNS_QR;
  if (is_fail)
    tmp |= DNS_RCODE_NAME_ERROR;
  dh->flags = clib_host_to_net_u16 (tmp);
  dh->qdcount = clib_host_to_net_u16 (1);
  dh->anscount = (is_fail == 0) ? clib_host_to_net_u16 (1) : 0;
  dh->nscount = 0;
  dh->arcount = 0;

  /* If the name resolution worked, cough up an appropriate RR */
  if (is_fail == 0)
    {
      /* Add the answer. First, a name pointer (0xC00C) */
      vec_add1 (reply, 0xC0);
      vec_add1 (reply, 0x0C);

      /* Now, add single A-rec RR */
      if (pr->request_type == DNS_PEER_PENDING_NAME_TO_IP)
	{
	  vec_add2 (reply, rrptr, sizeof (dns_rr_t) + sizeof (ip4_address_t));
	  rr = (dns_rr_t *) rrptr;

	  rr->type = clib_host_to_net_u16 (DNS_TYPE_A);
	  rr->class = clib_host_to_net_u16 (1 /* internet */ );
	  rr->ttl = clib_host_to_net_u32 (ttl);
	  rr->rdlength = clib_host_to_net_u16 (sizeof (ip4_address_t));
	  clib_memcpy (rr->rdata, rnr->ip4_address, sizeof (ip4_address_t));
	}
      else
	{
	  /* Or a single PTR RR */
	  u8 *vecname = format (0, "%s", rir->name);
	  u8 *label_vec = name_to_labels (vecname);
	  vec_free (vecname);

	  vec_add2 (reply, rrptr, sizeof (dns_rr_t) + vec_len (label_vec));
	  rr = (dns_rr_t *) rrptr;
	  rr->type = clib_host_to_net_u16 (DNS_TYPE_PTR);
	  rr->class = clib_host_to_net_u16 (1 /* internet */ );
	  rr->ttl = clib_host_to_net_u32 (ttl);
	  rr->rdlength = clib_host_to_net_u16 (vec_len (label_vec));
	  clib_memcpy (rr->rdata, label_vec, vec_len (label_vec));
	  vec_free (label_vec);
	}
    }
  clib_memcpy (dns_response, reply, vec_len (reply));

  /* Set the packet length */
  b0->current_length = sizeof (*ip) + sizeof (*udp) + vec_len (reply);

  /* IP header */
  ip->ip_version_and_header_length = 0x45;
  ip->length = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0));
  ip->ttl = 255;
  ip->protocol = IP_PROTOCOL_UDP;
  ip->src_address.as_u32 = src_address->as_u32;
  clib_memcpy (ip->dst_address.as_u8, pr->dst_address,
	       sizeof (ip4_address_t));
  ip->checksum = ip4_header_checksum (ip);

  /* UDP header */
  udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dns);
  udp->dst_port = pr->dst_port;
  udp->length = clib_host_to_net_u16 (sizeof (udp_header_t) +
				      vec_len (reply));
  udp->checksum = 0;
  vec_free (reply);

  /* Ship it to ip4_lookup */
  f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
  to_next = vlib_frame_vector_args (f);
  to_next[0] = bi;
  f->n_vectors = 1;
  vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */