/*****************************************************************************
 *
 * Date:    3 Nov 2005
 * Author:  Daniel Stenberg <daniel@haxx.se>
 * License: freely available to do whatever you want with.
 *
 * roundrobin.c - test the round robin DNS features of the resolver functions
 *
 * Note: this test script is written to be compiled and run on Linux. It is
 * not as portable as it could be, but that is just to make it a simpler test
 * case.
 *
 * "gcc roundrobin.c"
 *
 * This source snippet resolves a name with multiple IP addresses and prints
 * them out in the order the addresses were returned by the resolving
 * function. It first uses getaddrinfo() (called GAI) and then gethostbyname()
 * (called GHBN).
 *
 * On my three test machines they both show the same sympthoms:
 *
 * The GAI list is a lot less "random" than the GHBN one. The GAI list almost
 * always returns the same first entry on repeated invokes (while the
 * subsequent entries comes somewhat more random). The GHBN list is returned
 * in a much more random fashion.
 *
 * The test machines are all running Debian Unstable glibc 2.3.5
 *
 * What this program does:
 *
 * It runs N resolves of a given host names. It stores the order it gets the
 * returned addresses. When all N resolves are done, it checks how the
 * returned addresses were distributed. The procedure is first done with GAI
 * and then with GHBN. The output is presented in list index order. That
 * means: 'index 0' is the first address in the returned list and 'index 1' is
 * the second address and so on. We have found out that in the GAI case you
 * very often get 100% of the same address in index 0.
 *
 * We have three hosts names that resolves to multiple IP addresses:
 *
 * bad2.haxx.se
 * bad10.haxx.se
 * bad11.haxx.se
 *
 * As you will see, none of them resolves any sensible data for other purposes
 * than resolve tests or similar.
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>


static const char *printable_address(const struct addrinfo *ip,
                                     char *buf, size_t bufsize)
{
  const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr;
  int af = ip->ai_family;
  const void *ip6 = NULL;

  return inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize);
}

/* maximum number of test rounds we keep data for */
#define MAX_TESTS 2000

/* maximum number of ips returned for a name */
#define MAX_IPS 10

long counters[MAX_IPS][MAX_TESTS];
int lap;

static void dump_addrinfo(const char *str,
                          const struct addrinfo *ai,
                          int *numips)
{
  int addr=0;
  for ( ; ai; ai = ai->ai_next, addr++) {
    char  buf[INET6_ADDRSTRLEN];

#if 0
    /* output the address */
    printf("%s fam %2d, CNAME %s, ", str,
           ai->ai_family, ai->ai_canonname ? ai->ai_canonname : "<none>");
#endif
    if (printable_address(ai, buf, sizeof(buf))) {
#if 0
      /* show the "printable" format version of the address */
      printf("%s\n", buf);
#endif
      counters[addr][lap] = inet_addr(buf);
    }
  }
  *numips = addr;
}

static struct addrinfo *he2ai(struct hostent *he)
{
  struct addrinfo *ai;
  struct addrinfo *prevai = NULL;
  struct addrinfo *firstai = NULL;
  struct sockaddr_in *addr;
  int i;
  struct in_addr *curr;

  for(i=0; (curr = (struct in_addr *)he->h_addr_list[i]); i++) {

    ai = calloc(1, sizeof(struct addrinfo) + sizeof(struct sockaddr_in));

    if(!ai)
      break;

    if(!firstai)
      firstai = ai;

    if(prevai)
      prevai->ai_next = ai;

    ai->ai_family = AF_INET;
    ai->ai_socktype = SOCK_STREAM;
    ai->ai_addrlen = sizeof(struct sockaddr_in);
    ai->ai_addr = (struct sockaddr *) ((char*)ai + sizeof(struct addrinfo));

    addr = (struct sockaddr_in *)ai->ai_addr; /* storage area for this info */
    memcpy((char *)&(addr->sin_addr), curr, sizeof(struct in_addr));
    addr->sin_family = he->h_addrtype;
    addr->sin_port = htons((unsigned short)80);

    prevai = ai;
  }
  return firstai;
}

int sumip[MAX_IPS];
int sum[MAX_IPS];

static report(int numips, int numtests)
{
  int i, j;

  printf("--- %d different IPs were returned\n", numips);

  for(i=0; i<numips; i++) {
    printf("IP index %d\n", i);
    memset(sum, 0, sizeof(sum));

    for(lap=0; lap< numtests; lap++) {
#if 0
      /* show each address for each test lap */
      printf(" %x", counters[i][lap]);
#endif
      for(j=0; j<numips; j++) {
        if(sumip[j] == counters[i][lap])
          sum[j]++;
      }
    }
#if 0
    /* end of line of the previous output */
    printf("\n ");
#endif
    for(j=0; j<numips; j++) {
      if(sum[j])
        printf(" %x %d times (%d%%)\n", sumip[j], sum[j], sum[j]*100/numtests);
    }
    printf("\n");

  }
}


#define HOSTNAME argv[1]

int main(int argc, char **argv)
{
  struct addrinfo hints, *res;
  int error;
  struct hostent *h;
  int numtests = 5;
  int numips;
  int i, j;

  if(argc<2) {
    printf("roundrobin [host] [number of resolves]\n");
    return 2;
  }

  if(argc>2) {
    numtests = atoi(argv[2]);
    if(numtests > MAX_TESTS) {
      printf("too many, increase MAX_TESTS forst\n");
      return 1;
    }
  }

  memset(&hints, 0, sizeof(hints));
  hints.ai_family = PF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  /* hints.ai_flags = AI_CANONNAME makes no apparent difference */

  printf("---Check %d runs of getaddrinfo(%s)\n", numtests, HOSTNAME);

  for(lap=0; lap< numtests; lap++) {

    error = getaddrinfo(HOSTNAME, "80", &hints, &res);
    if (error) {
      printf("getaddrinfo(3) failed\n");
      return 1;
    }
    else {
      dump_addrinfo("getaddrinfo", res, &numips);
    }
    freeaddrinfo(res);
  }

  for(i=0; i<numips; i++)
    /* get each IP address binary blob */
    sumip[i]= counters[i][0];

  report(numips, numtests);

  printf("---Check %d runs of gethostbyname(%s)\n", numtests, HOSTNAME);

  for(lap=0; lap< numtests; lap++) {

    h = gethostbyname(HOSTNAME);
    if(h) {
      res = he2ai(h);
      dump_addrinfo("gethostbyname", res, &numips);
    }
  }

  report(numips, numtests);

  return 0;
}

