/*
 * echoping : uses the TCP echo service to measure (roughly) response times.
 * 
 * Written by Stephane Bortzmeyer <bortzmeyer@pasteur.fr>. A lot of code stolen
 * from Richard Stevens' book "Unix network programming" and Pierre Beyssac's
 * "bing" tool. 
 * 
 */

char *progname;
unsigned short timeout_flag;

#include	"inet.h"

/*
 * An option to define only if you want to drive echoping from another
 * process. Useless but harmless otherwise. In practice, while OSF/1 is happy
 * with it, SunOS refuses to use fflush on a NULL and Linux fails.
 */
#undef FLUSH_OUTPUT

/* Global variables for main and printstats */

int return_code = 0;
unsigned int number = 1;
struct timeval max, min, total, median, temp;
unsigned int successes, attempts = 0;
unsigned int size = DEFLINE;
unsigned int j = 0;
struct result
  {
    unsigned short valid;
    struct timeval timevalue;
  };
struct result results[MAXNUMBER];
struct timeval good_results[MAXNUMBER];
extern int tvcmp ();

void
main (argc, argv)
     int argc;
     char *argv[];
{
  extern char *optarg;
  extern int optind;

  signed char ch;

  int sockfd;
  struct hostent *hostptr;
  struct sockaddr_in serv_addr;
  struct sockaddr_in udp_cli_addr;	/* client's Internet socket
					 * addr */
  struct servent *sp;
  int verbose = FALSE;
  char *server_address;
  u_int addr;
  struct in_addr *ptr;
  int n, nr;
  char *sendline, recvline[MAXLINE + 1];
  struct timeval newtv, oldtv;
  void printstats ();

  unsigned int wait = 1;
  unsigned char fill = ' ';
  unsigned short fill_requested = 0;
  unsigned int i = 0;

  void to_alarm ();		/* our alarm() signal handler */
  void interrupted ();
  unsigned int timeout = 10;
  unsigned short timeout_requested = 0;
  char *url = "";
  short port = 0;
  char *text_port = malloc (6);

  char *port_name = ECHO_TCP_PORT;
  unsigned short port_to_use = USE_ECHO;
  unsigned short http = 0;
  unsigned short udp = 0;
  unsigned short ttcp = 0;
  unsigned short tcp = 0;

  unsigned short stop_at_newlines = 1;

  null_timeval.tv_sec = 0;
  null_timeval.tv_usec = 0;
  max_timeval.tv_sec = 1000000000;
  max_timeval.tv_usec = 999999;

  return_code = 0;
  number = 1;
  total = null_timeval;
  median = null_timeval;
  max = null_timeval;
  min = max_timeval;

  for (i = 0; i <= MAXNUMBER; i++)
    {
      results[i].valid = 0;
    }
  progname = argv[0];
  while ((ch = getopt (argc, argv, "vs:n:w:dch:rut:f:")) != EOF)
    {
      switch (ch)
	{
	case 'v':
	  verbose = TRUE;
	  break;
	case 'r':
	  ttcp = 1;
	  break;
	case 'u':
	  udp = 1;
	  break;
	case 'd':
	  port_name = DISCARD_TCP_PORT;
	  port_to_use = USE_DISCARD;
	  break;
	case 'c':
	  port_name = CHARACTER_GENERATOR_TCP_PORT;
	  port_to_use = USE_CHARGEN;
	  stop_at_newlines = 0;
	  break;
	case 'h':
	  port_name = HTTP_TCP_PORT;
	  port_to_use = USE_HTTP;
	  http = 1;
	  url = optarg;
	  break;
	case 'f':
	  fill = *optarg;
	  fill_requested = 1;
	  break;
	case 's':
	  size = atoi (optarg);
	  if (size > MAXLINE)
	    {
	      (void) fprintf (stderr,
			      "%s: packet size too large, max is %d.\n",
			      progname, MAXLINE);
	      exit (1);
	    }
	  if (size <= 0)
	    {
	      (void) fprintf (stderr,
			      "%s: illegal packet size.\n", progname);
	      exit (1);
	    }
	  break;
	case 't':
	  timeout = atoi (optarg);
	  timeout_requested = 1;
	  if (size <= 0)
	    {
	      (void) fprintf (stderr,
			      "%s: illegal timeout.\n", progname);
	      exit (1);
	    }
	  break;
	case 'n':
	  number = atoi (optarg);
	  if (number > MAXNUMBER)
	    {
	      (void) fprintf (stderr,
			 "%s: number of iterations too large, max is %d.\n",
			      progname, MAXNUMBER);
	      exit (1);
	    }
	  if (number <= 0)
	    {
	      (void) fprintf (stderr,
			   "%s: illegal number of iterations.\n", progname);
	      exit (1);
	    }
	  break;
	case 'w':
	  wait = atoi (optarg);
	  if (wait <= 0)
	    /* atoi returns zero when there is an error. So we cannot use 
	       '-w 0' to specify no waiting. */
	    {
	      (void) fprintf (stderr,
			      "%s: illegal waiting time.\n", progname);
	      exit (1);
	    }
	  break;
	default:
	  usage ();
	}
    }
  if (udp && ((port_to_use == USE_CHARGEN) || (port_to_use == USE_HTTP)))
    {
      (void) fprintf (stderr,
	     "%s: I don't know how to use this port with UDP.\n", progname);
      exit (1);
    }
  if (!udp && (timeout_requested))
    {
      (void) fprintf (stderr,
		   "%s: Time out ignored for TCP connections.\n", progname);
      exit (1);
    }
  if (http && (fill_requested))
    {
      (void) fprintf (stderr,
	     "%s: Filling incompatible with HTTP connections.\n", progname);
      exit (1);
    }
#ifndef TTCP
  if (ttcp)
    {
      (void) fprintf (stderr,
		      "%s: not compiled with T/TCP support.\n", progname);
      exit (1);
    }
#endif
#ifndef HTTP
  if (http)
    {
      (void) fprintf (stderr,
		      "%s: not compiled with HTTP support.\n", progname);
      exit (1);
    }
#endif
  if (udp && ttcp)
    {
      (void) fprintf (stderr,
		      "%s: UDP and T/TCP are incompatible.\n", progname);
      exit (1);
    }
  if (!udp && !ttcp)
    {
      tcp = 1;
    }
  argc -= optind;
  argv += optind;
  if (argc != 1)
    {
      usage ();
    }
  if (verbose)
    {
      printf ("\nThis is %s, version %s.\n\n", progname, VERSION);
    }
  server = argv[0];
  if (http)
    {
      find_server_and_port (server, &port);
      sprintf (text_port, "(port %d)", port);
    }
  signal (SIGINT, interrupted);
  if ((addr = inet_addr (server)) == INADDR_NONE)
    {
      if ((hostptr = gethostbyname (server)) == NULL)
	{
	  err_quit ("gethostbyname error for host: %s %s",
		    server, sys_err_str ());
	}
      server_address = *(hostptr->h_addr_list);		/* First item of the
							 * list */
      /*
       * addr = (u_long) *server_address; 
       */
      /* ptr.s_addr = addr; */
      ptr = (struct in_addr *) server_address;	/* hostptr->h_addr_list
						 * points actually to
						 * u_longs, not strings */
      addr = ptr->s_addr;
    }
  else
    {
      ptr = (struct in_addr *) malloc (sizeof (struct in_addr));
      ptr->s_addr = addr;
    }
  if (!udp)
    {
      if ((sp = getservbyname (port_name, "tcp")) == NULL)
	{
	  err_quit ("tcp_open: unknown service: %s/tcp", port_name);
	}
    }
  else
    {
      if ((sp = getservbyname (port_name, "udp")) == NULL)
	{
	  err_quit ("tcp_open: unknown service: %s/udp", port_name);
	}
    }
  /*
   * Fill in the structure "serv_addr" with the address of the server
   * that we want to connect with.
   */

  bzero ((char *) &serv_addr, sizeof (serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = addr;
  if (!http)
    {
      serv_addr.sin_port = sp->s_port;
    }
  else
    {
      serv_addr.sin_port = htons (port);
    }

#ifdef HTTP
  if (http)
    {
      sendline = make_http_sendline (url);
    }
  else
#endif
  if (!fill_requested)
    {
      sendline = random_string (size);
    }
  else
    {
      sendline = (char *) malloc (size);
      for (i = 0; i < size; i++)
	sendline[i] = fill;
    }
  n = strlen (sendline) + 1;

  for (i = 1; i <= number; i++)
    {

      attempts++;
      if (!udp)
	{
	  /*
	   * Open a TCP socket (an Internet stream socket).
	   */

	  if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	    err_sys ("Can't open stream socket");
	}
      else
	{
	  if ((sockfd = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
	    err_sys ("Can't open datagram socket");
	  /* Bind socket for reply. Not necessary? */
	  bzero ((char *) &udp_cli_addr, sizeof (udp_cli_addr));
	  udp_cli_addr.sin_family = AF_INET;
	  udp_cli_addr.sin_addr.s_addr = htonl (INADDR_ANY);
	  udp_cli_addr.sin_port = htons (0);
	  if (bind (sockfd, (struct sockaddr *) &udp_cli_addr,
		    sizeof (udp_cli_addr)) < 0)
	    {
	      err_sys ("bind error");
	    }
	}
      if (verbose)
	{
	  if (tcp)
	    {
	      printf ("Trying to connect to internet address %s %s to transmit %u bytes...\n",
		      inet_ntoa (*ptr), (port == 0 ? "" : text_port), n);
	    }
	  else
	    {
	      printf ("Trying to send %u bytes to internet address %s...\n",
		      size, inet_ntoa (*ptr));
	    }
#ifdef FLUSH_OUTPUT
	  if (fflush ((FILE *) NULL) != 0)
	    {
	      err_sys ("I cannot flush");
	    }
#endif
	}
      (void) gettimeofday (&oldtv, (struct timezone *) NULL);

      if (!ttcp)
	{
	  /*
	   * Connect to the server.
	   */

	  if (connect (sockfd, (struct sockaddr *) &serv_addr,
		       sizeof (serv_addr)) < 0)
	    err_sys ("Can't connect to server");
	  if (verbose && tcp)
	    {
	      printf ("Connected...\n");
#ifdef FLUSH_OUTPUT
	      if (fflush ((FILE *) NULL) != 0)
		{
		  err_sys ("I cannot flush");
		}
#endif
	    }
	}
      /* Not T/TCP */
      else
	{
	  /* No initial connection */
	}
      if ((port_to_use == USE_ECHO) || (port_to_use == USE_DISCARD) ||
	  (port_to_use == USE_HTTP))
	{
#ifdef TTCP
	  if (ttcp)
	    {
	      if (sendto (sockfd, sendline, n, MSG_EOF,
		   (struct sockaddr *) &serv_addr, sizeof (serv_addr)) != n)
		err_sys ("sendto error on socket");
	      if (verbose)
		{
		  printf ("T/TCP connection done\n");
		}
	    }
	  else
#endif
	  if (!udp)
	    {
	      /* Write something to the server */
	      if (writen (sockfd, sendline, n) != n)
		err_sys ("writen error on socket");
	    }
	  else
	    {
	      /*
	       * if (sendto(sockfd, sendline, n, 0,
	       * &serv_addr, sizeof(serv_addr)) != n)
	       * err_sys("sendto error on socket");
	       */
	      if (send (sockfd, sendline, n, 0) != n)
		err_sys ("send error on socket");
	    }
	  if (verbose)
	    {
	      printf ("Sent (%d bytes)...\n", n);
#ifdef FLUSH_OUTPUT
	      if (fflush ((FILE *) NULL) != 0)
		{
		  err_sys ("I cannot flush");
		}
#endif
	    }
	}
      if ((port_to_use == USE_ECHO) || (port_to_use == USE_CHARGEN) || (port_to_use == USE_HTTP))
	{
	  if (!udp)
	    {
	      if (!http)
		{
		  /* Read from the server */
		  nr = readline (sockfd, recvline, n, stop_at_newlines);
		}
	      else
		{
		  nr = read_from_server (sockfd);
		}
	    }
	  else
	    {
	      signal (SIGALRM, to_alarm);
	      timeout_flag = 0;	/* for signal handler */
	      alarm (timeout);
	      nr = recv (sockfd, recvline, n, 0);
	      /*
	       * nr = recvfrom(sockfd, recvline, n, 0,
	       * (struct sockaddr *) 0, (int *) 0);
	       * recvfrom fails on SunOS on connected
	       * sockets.
	       */
	      /*
	       * BUG: in UDP, we should loop to read: we
	       * can have several reads necessary.
	       */
	      alarm (0);
	      if ((nr < 0) && (errno == EINTR) && (timeout_flag))
		{
		  nr = n;
		  printf ("Timeout\n");
#ifdef FLUSH_OUTPUT
		  if (fflush ((FILE *) NULL) != 0)
		    {
		      err_sys ("I cannot flush");
		    }
#endif
		}
	    }
	  if (!http)
	    {
	      if (nr < 0 || nr != n)
		err_sys ("readline error: %d bytes read, %d bytes requested", nr, n);
	    }
	  else
	    {
	      if (verbose)
		printf ("%d bytes read from server.\n", nr);
	    }
	}
      /* That's all, folks */
      close (sockfd);

      (void) gettimeofday (&newtv, (struct timezone *) NULL);
      temp = newtv;
      tvsub (&temp, &oldtv);
      if (!timeout_flag)
	{
	  tvadd (&total, &temp);

	  /* Check */
	  if (port_to_use == USE_ECHO)
	    {
	      if (strcmp (sendline, recvline) != 0)
		{
		  printf (" I wrote:\n%s\n", sendline);
		  printf (" and I got back:\n%s\n", recvline);
		  err_quit ("Strange server");
		}
	      if (verbose)
		{
		  printf ("Checked\n");
#ifdef FLUSH_OUTPUT
		  if (fflush ((FILE *) NULL) != 0)
		    {
		      err_sys ("I cannot flush");
		    }
#endif
		}
	    }
	  if (port_to_use == USE_CHARGEN)
	    {
	      sendline = CHARGENERATED;
	      recvline[strlen (sendline)] = 0;
	      if (strcmp (sendline, recvline) != 0)
		{
		/* BUG: it does not work if the size is lower than the
		length of CHARGENERATED */
		  printf (" I got back:\n%s\n", recvline);
		  printf (" instead of the most common:\n%s\n", sendline);
		  err_ret ("Strange server");
		}
	      if (verbose)
		{
		  printf ("Checked\n");
		}
	    }
	  tvsub (&newtv, &oldtv);
	  tvmin (&min, &newtv);
	  tvmax (&max, &newtv);
	  printf ("Elapsed time: %d.%06d seconds\n", newtv.tv_sec, newtv.tv_usec);
#ifdef FLUSH_OUTPUT
	  if (fflush ((FILE *) NULL) != 0)
	    {
	      err_sys ("I cannot flush");
	    }
#endif
	  results[i - 1].valid = 1;
	  results[i - 1].timevalue = newtv;
	  successes++;
	}
      if (number > 1)
	{
	  sleep (wait);
	}
    }
  printstats ();
  /* Bug: in UDP, we exit with 0 (success) even if all UDP requests failed. */
  exit (0);
}


void
printstats ()
{

  int i;

  /* if ((number > 1) && ((!udp) || (successes > 0))) { */
  if (successes > 1)
    {
      printf ("---\n");
      if (successes < attempts)
	printf ("Warning: %d messages lost (%d %%)\n", attempts - successes,
		((attempts - successes) * 100) / attempts);
      printf ("Minimum time: %d.%06d seconds (%.0f bytes per sec.)\n",
	      min.tv_sec, min.tv_usec, (double) size / tv2double (min));
      printf ("Maximum time: %d.%06d seconds (%.0f bytes per sec.)\n",
	      max.tv_sec, max.tv_usec, (double) size / tv2double (max));
      tvavg (&total, successes);
      printf ("Average time: %d.%06d seconds (%.0f bytes per sec.)\n",
	    total.tv_sec, total.tv_usec, (double) size / tv2double (total));
      /* The number of bytes/second, as printed above, is not really
         meaningful: size does not reflect the number of bytes exchanged.
         With echo, N = 2*size, with discard, N = size, with http, N = size + (response)... */
      for (i = 0; i < number; i++)
	{
	  if (results[i].valid)
	    good_results[j++] = results[i].timevalue;
	}
      if (successes != j)	/* Bug! */
	err_quit ("successes (%d) is different from j (%d)", successes, j);
      qsort (good_results, successes, sizeof (struct timeval), tvcmp);
      /*
       * for (i = 1; i <= number; i++) { printf("---\nTime %d th:
       * %d.%06d seconds\n", i, results[i-1].tv_sec,
       * results[i-1].tv_usec);       }
       */
      if ((successes % 2) == 1)
	{
	  /*
	   * printf("Searching good_results[%d]\n", (successes
	   * + 1) / 2 - 1);
	   */
	  median = good_results[((successes + 1) / 2 - 1)];
	}
      else
	{
	  /*
	   * printf("Searching good_results[%d] and [%d]\n",
	   * (successes / 2) - 1, successes / 2);
	   */
	  tvadd (&median, &good_results[(successes / 2) - 1]);
	  tvadd (&median, &good_results[successes / 2]);
	  tvavg (&median, 2);
	}
      printf ("Median time: %d.%06d seconds (%.0f bytes per sec.)\n",
	 median.tv_sec, median.tv_usec, (double) size / tv2double (median));
    }
}

/*
 * Signal handler for timeouts (SIGALRM). This function is called when the
 * alarm() value that was set counts down to zero.  This indicates that we
 * haven't received a response from the server to the last datagram we sent.
 * All we do is set a flag and return from the signal handler. The occurrence
 * of the signal interrupts the recvfrom() system call (errno = EINTR) above,
 * and we then check the tout_flag flag.
 */

void
to_alarm ()
{
  timeout_flag = 1;		/* set flag for function above */
}

void
interrupted ()
{
  printf ("Interrupted by user\n");
  printstats ();
  exit (1);
}
