#!/usr/bin/perl

use Socket;
use Fcntl;

# run as daemon
#$pid = fork;
#exit if $pid;
#die "Couldn't fork: $!" unless defined($pid);
#use POSIX;
#POSIX::setsid()
#    or die "Can't start a new session: $!";

# parse command line arguments
$server_type = $ARGV[0];
$IPERF = $ARGV[1];
$server_port = $ARGV[2];


print "server_type: '$server_type'\n'";


#check the server type
if ( $server_type != "client" || $server_type != "server" ) {
  die "Usage: iperf.pl {client|server} <iperf executeable> <port>\n";
}

#check for iperf existance
if ( $IPERF eq '' ) {
  $IPERF = `which iperf`;
  chomp( $IPERF );
  if ( $IPERF eq '' ) {
    die "iperf executeable $IPERF not found!\n";
  }
}
else {
  # check to see if it exists
}

# set up port to listen to
if ( $server_port eq '' ) {
  $server_port = 10001;
}

#set the password
$password = 'gimme';


#make the socket
socket( SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp') );

#reuse the address
setsockopt( SERVER, SOL_SOCKET, SO_REUSEADDR, 1 );

#build up a port to listen to
$addr = sockaddr_in( $server_port, INADDR_ANY );
bind( SERVER, $addr )
  or die "Couldn't bind to port $server_port : $!\n";

#establish a queue to incoming connections
listen( SERVER, SOMAXCONN )
  or die "Couldn't listen on port $server_port : $!\n";


# loop indefinately
while (1)
  {

    print STDOUT "Awaiting Connection....\n";

    # accept the connection and do stuff
    while( $client_add = accept( CLIENT, SERVER ) )
      {
	
	$client_socket = getpeername( CLIENT )
	  # don't die - do last
	  or die "Couldn't identify client! : $!\n";
	( $client_port, $client_add ) = unpack_sockaddr_in( $client_socket );
	$client_ip = inet_ntoa( $client_add );
	
	# set up no buffering
	$old_fh = select( CLIENT );
	$| = 1;
	select( $old_fh );

	#do something
	print STDOUT "Connected to $client_ip ($client_port)\n";
	print STDOUT "====================================\n";

	my $iperf_pid;

	# always listen for commands - blocking until one comes
	# TODO: timeouts
	while ( $args = <CLIENT> ) 
	  {
	    
	    # convert the command to something useful
	    %iperf = &parse_server_args( $args );
	    
	    # check the password - if it doesn't match close connection
	    if ( $iperf{password} ne $password ) {
		print "wrong password! $iperf{password}\n";
	#      last;
	    }

	    # test start - initiate iperf
	    if ( $iperf{CMD} eq 'START' )
	      {
		if ( $server_type eq 'server' ) {
		  printf STDOUT "\nGOT %s) type=%s, port=%d, interval=%d\n", $iperf{CMD}, $iperf{type},$iperf{port}, $iperf{report_interval};
		  $iperf_pid = &start_iperf_server( $iperf );
		}
		elsif ( $server_type eq 'client' ) {
		  $iperf_pid = &start_iperf_client( $iperf );
		}
	      }
	    # get stats - return data to peer
	    elsif ( $iperf{CMD} eq 'GET_STATS' )
	      {
		print STDOUT "GOT STATS\n";
		$output = &get_iperf_stats( $iperf_pid );
		print STDOUT "OUTPUT:\n$output\n\n";
		
		#TODO: return $output back to CLIENT
		# replace newlines with special char ===newline===
		$output =~ s/\n/===newline===/g;
		print CLIENT "$output\n";

	      }
	    # end - finished
	    elsif ( $iperf{CMD} eq 'END' )
	      {
		print STDOUT "GOT END\n";
		&kill_iperf( $iperf_pid );
		last;
	      }
	    # other - finish
	    else
	      {
		print STDOUT "ERROR\n";
		&kill_iperf( $iperf_pid );
		last;
	      }
	    	    
	  } # while recv

	print STDOUT "====================================\n";

      } # while accept
   
    close( SERVER );

  } # while




########################################################################

sub start_iperf_server {

  my ( $iperf ) = @_;

  # create the command line of the iperf server from input args
  # and divert the reponse from the server to a temp file
  if ( $iperf{type} eq 'tcp' ) {
    $cmd = "$IPERF -s -p $iperf{port} -w $iperf{socketbuffer} -i $iperf{report_interval} -fb";
  }
  else {
    $cmd = "$IPERF -u -s -l $iperf{packet_size} -p $iperf{port} -w $iperf{socketbuffer} -i $iperf{report_interval} -fb";
  }

#  print "CMD: $cmd\n";

  $cmd =~ s/\n//g;

  # run the command, collecting it's output
  $pid = open( README, "$cmd |" )
    or die "Couldn't fork iperf: $!\n";

  # change the filehandle to nonblocking
  $flags = '';
  fcntl( README, F_GETFL, $flags )
    or die "Couldn't get flags for README: $!\n";
  $flags |= O_NONBLOCK;
  fcntl( README, F_SETFL, $flags )
    or die "Couldn't set flags for README: $!\n";

  print "Running '$cmd' on pid $pid\n";

  return $pid;

}


sub start_iperf_client {

  my ( $iperf ) = @_;

  # create the command line of the iperf client from input args
  # and divert the reponse from the server to a temp file
  
  my $cmd = "$IPERF -c $iperf{destination} -p $iperf{port} -t $iperf{duration} -w $iperf{socketbuffer} -i $iperf{report_interval} -fb --tos $iperf{tos}";

  if ( $iperf{type} eq 'tcp' ) {
    # clear the proc ssthresh value?
  }
  else {
    $cmd .= " -u -l $iperf{packet_size} -b $iperf{target_rate}";
  }

  $cmd =~ s/\n//g;

#  print "CMD: $cmd\n";

  # run the command, collecting it's output
  $pid = open( README, "$cmd |" )
    or die "Couldn't fork iperf client: $!\n";

  # change the filehandle to nonblocking
  $flags = '';
  fcntl( README, F_GETFL, $flags )
    or die "Couldn't get flags for README: $!\n";
  $flags |= O_NONBLOCK;
  fcntl( README, F_SETFL, $flags )
    or die "Couldn't set flags for README: $!\n";

  print "Running '$cmd' on pid $pid\n";

  return $pid;

}
  



sub get_iperf_stats {

  # read the output and store in temporary field
  my $iperf_output;

  # determine how much data to read: NB only works if ioctl exists
  $size = pack("L", 0);
  # NB hard coded: need to compile and run fionread exe to find out value
  $FIONREAD = 0x00541b;
  ioctl( README, $FIONREAD, $size)
    or die "Couldn't call ioctl: $!\n";
  $size = unpack("L", $size);

  # read the filehandle contents and append to iperf_output
  $rv = sysread( README, $buffer, $size );
  if ( !defined($rv) && $! == EAGAIN ) {
    print "blocked\n";
  } else {
    $iperf_output .= $buffer;
  }

  return $iperf_output;

}


sub kill_iperf {

  my( $iperf_pid ) = @_;

  # kill the process
  return `kill $iperf_pid`;

}



sub parse_server_args {

  my ($input) = @_;

  chomp($input);

  my @line = split /,/, $input;

  # TODO: parameter checking?

  my %iperf;

  foreach my $arg (@line) {
    my( $var, $value ) = split /=/, $arg;
#    print "ARG: $arg, VAR: $var, VALUE: $value\n";
    $iperf{$var} = $value;
  }

  return %iperf;

}
