/*******************************************************************************
* File: dns64perf2.cc -- Modified version of dns64perf.cc
*
* Desription: Simple C/C++ program for performace testing of DNS64 servers
*
* Input parameters: a=argv[1], b=argv[2], n=argv[3] (MUST be a power of 2), 
                    timeout=argv[4],
*                   server=argv[5] (IPv6 address of the tested DNS64 server),
*                   port=argv[6] (port number is optional, default value: 53)
*
* Operation: b*256*256 DNS AAAA queries are sent for the following names:
*            a-{0..b-1}-{0..255}-{0..255}.dns64perf.test using n threads
*
* Pseudocode: for q in {0..256*b-1}
*               measure the time of the following
*                 use n threads, let c take 256/n distinct values in each thread
*                   query AAAA record for a-(q/256)-(q%256)-c.dns64perf.test
*
* Author: Gabor Lencse, lencse@hit.bme.hu
*
* Documented in:
* G. Lencse, "Performance Analysis of MTD64, our Tiny Multi-Threaded DNS64 Server 
* Implementation: Proof of Concept",
* submitted for review to: International Journal of Advances in Telecommunications,
* Electrotechnics, Signals and Systems
* Review version available:
* http://www.hit.bme.hu/~lencse/publications/
*
* Note: C++ is used for thread handling only.
*
* Copyright (C) 2016 Gabor Lencse
* Source code avalable: http://www.hit.bme.hu/~lencse/dns64perf2/
* License: GNU GPLv3, available: http://www.gnu.org/licenses/gpl-3.0.en.html
*
* This program is based on the old "dns64perf" program, which was documented in:
* G. Lencse, "Test Program for the Performance Analysis of DNS64 Servers",
* International Journal of Advances in Telecommunications, Electrotechnics, 
* Signals and Systems, vol. 4. no. 3. (September 2015.) pp. 60-65.
* DOI: 10.11601/ijates.v4i3.121
* http://www.hit.bme.hu/~lencse/publications/IJATES2-2015-dns64perf-published.pdf
*******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/un.h>
#include <memory.h>
#include <sys/time.h>
#include <time.h>
#include <arpa/inet.h>
#include <thread>


#define UDPSIZE (512+8)         /* max. size of UDP packets with DNS messages */
#define SERVERPORTS "53"        /* default server port as string */


// encode the DNS name -- RFC 1035
int dnsencode(char *dnsname, char *query)
{
  int i, len;

  len=0;
  while ( *dnsname )
  {  
    for (i=0; dnsname[i] && dnsname[i]!='.'; i++)
      query[i+1]=dnsname[i];    // copy the label
    if ( dnsname[i] )            
    {  // dnsname[i] is '.'     // a non-last label is finished 
      query[0]=i;               // store the length of the label
      query+=i+1;		// adjust destination pointer
      dnsname+=i+1;		// adjust source pointer
      len+=i+1;			// account length
    }
    else
    {  // dnsname[i] is '\0'    // the last label is finished
      query[0]=i;               // store the length of the label
      query[i+1]='\0';		// append the terminating '\0' char
      dnsname+=i;		// adjust source pointer
      len+=i+2;			// account length (terminating '\0' also needs space)
    }
  }
  return len;
}
    

int dnstest(int a, int q, int c0, int N, struct timeval *ptimeout, struct addrinfo *aiServer, int portn)
{
  int len;
  char query[258], dnsname[256], dnspre[251];
  const char *dnspost=".dns64perf.test";

  int s;
  int c,cN;

  char request[UDPSIZE];        // buffer for the UDP packet of the DNS reqest
  char reply[UDPSIZE];          // buffer for the UDP packet of the DNS reply

  struct sockaddr_in6 sa;       // parameters for sendto 
  struct sockaddr_in6 sar;      // parameters for recvfrom
  int raddrlen;                 // addrlen for recvfrom

  sprintf(dnspre,"%i-%i-%i-",a,q/256,q%256); // common prefix for all queries

  if ( (s=socket(PF_INET6, SOCK_DGRAM, 0)) < 0 ) // create a socket
    {
    perror("socket()");
    return EXIT_FAILURE;
    }

  // fill the sa structure 
  sa.sin6_family = PF_INET6;
  sa.sin6_port = htons(portn);
  memcpy((char *)&sa.sin6_addr, (char *)&aiServer->ai_addr->sa_data+6, 16);

  //  set the socket timeout 
  setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)ptimeout,sizeof(struct timeval));

  // loop for sending N number of queries (and receiving the replies)
  for (c=c0,cN=c+N; c<cN; c++)
  {
    sprintf(dnsname,"%s%i%s",dnspre,c,dnspost); // DNS name: 10-a-b-c.dns64perf.test
    len=dnsencode(dnsname,query);

    // prepare the DNS query
    memset(request,0,len+16); // the whole message: 6x 2bytes + DNS name + 2x 2bytes
    request[0]=q%256; // Transaction ID 1st byte
    request[1]=c; // Transaction ID 2nd byte
    request[2]=1; // Standard Query
    request[5]=1; // Questions: 1
    memcpy(request+12,query,len);       // the encoded DNS name
    request[len+13]=0x1c; // Type: AAAA
    request[len+15]=1; // Class: IN

    // send the DNS query    
    if ( sendto(s, request, len+16, 0, (const struct sockaddr *)&sa, sizeof sa) < 0 )
      {
      perror("sendto()");
      return EXIT_FAILURE;
      }

    // receive DNS reply    
    raddrlen=sizeof sar;
    if ( (len=recvfrom(s, reply, UDPSIZE, 0, (struct sockaddr *)&sar, (socklen_t *)&raddrlen)) < 0 )
      {
      perror("recvfrom()");
      return EXIT_FAILURE;
      }

  }     // loop ends here
  close(s);
  return EXIT_SUCCESS;
}


int main(int argc, char *argv[])
{
  int a, b, c, N, n, cE, q;
  const char *server, *ports;   // IPv6 address and port number of the DNS64 server
  int portn;			// server port number (as integer)
  struct timespec start, end;   // for time measurement
  time_t sec;
  long nsec;
  double deltaT;
  struct timeval timeout;       // for DNS64 server timeout
  int i;
  struct addrinfo *aiServer;    // IP address etc. aquired by getaddrinfo()
  struct addrinfo aiHint={0,0,0,0,0,NULL,NULL,NULL};    // to specify v4 or v6 for getaddrinfo()
  int aiErr;
  std::thread *pThread;         // pointer for thread array

  if ( argc < 6 || argc > 7 )
  {
    printf("Usage: %s a b n t IPv6addr [port]\n",argv[0]);
    return EXIT_FAILURE;
  }

  sscanf(argv[1],"%i",&a);      // the 'a' value for a-q/256-q%256-c
  sscanf(argv[2],"%i",&b);      // the 'b' value for q in {0..256*b-1}
  sscanf(argv[3],"%i",&n);      // the number of threads, MUST be a power of 2
  sscanf(argv[4],"%i",&timeout.tv_sec); // timeout in seconds
  timeout.tv_usec=0;		// 0 microseconds 
  server=argv[5];               // IPv6 address of the DNS64 server to be tested
  if ( argc > 6 )
    ports=argv[6];              // server port number was specified
  else
    ports=SERVERPORTS;          // use the default value for server port number
  sscanf(ports,"%i",&portn);    // port number as integer 

  // determine and set the server parameters (e.g. IPv6 address) for TCP/IP communications 
  if ( (aiErr=getaddrinfo(server,ports,&aiHint,&aiServer)) != 0 )
    {
    fprintf(stderr,"getaddrinfo for DNS64 server %s failed, error message is: %s\n",
            server, gai_strerror(aiErr));
    return EXIT_FAILURE;
    }
  // now, aiServer contains everything what will be needed 

  pThread = (std::thread *) malloc(n * sizeof(std::thread));	// allocate memory for n threads

  N=256/n;                      // number of request to be sent by each threads 

  // main loop for b in {0..255}
  for (q=0; q<256*b; q++) 	// do 256*b experiments
  {
    clock_gettime(CLOCK_REALTIME,&start);

    // start n threads
    for (c=i=0; i<n; i++)
    {
      pThread[i]=std::thread(dnstest, a, q, c, N, &timeout, aiServer, portn);
      c+=N;
    }

    // wait until they all finish
    for (i=0; i<n; i++)
    {
      pThread[i].join();
    }

    clock_gettime(CLOCK_REALTIME,&end);
    nsec=end.tv_nsec-start.tv_nsec;
    sec=end.tv_sec-start.tv_sec;
    if ( nsec < 0 )
    {
      sec-=1;
      nsec+=1000000000;
    }
    deltaT=sec+0.000000001*nsec;
    printf("%lf\n",1000*deltaT); // the time of 256 AAAA queries and their answers
  }
}

