/*
NOMBRE:		mtb.c
SINOPSIS:	mtb <Puerto_Local> <IP_Proxy> <Puerto_Proxy>
DESCRIPCION:	Programa que funciona como servidor transparente entre el alimentador (browser) y el verdadero
				Proxy que sale a internet.
*/

#include <stdio.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>


pid_t pid;
FILE *sd;

void reusePort(int sock);
void alarm_cb (int signum);
void interrupt (int signum);
int pfd[2];
int enable=0,bytesTX=0,bytesRX=0;
int alarmtimer = 0;
int alarmcondition = 60;
int status;
/*Se crean arreglos para adquirir los datos y posteriormente graficar*/

int TotalbytesTX=0, Nconex=0, NTotalconex=0,TotalbytesRX=0;	// indicadores de bytes TX-RX y conexiones
struct Datos {
	int tiempo;
	int data;
	};
struct Datos datosRX[120], datosTX[120], datosConex[120];



main( argc, argv )
int argc;
char *argv[];
{
  int   sc, sp;	// descriptores de archivos para los socket del cliente y del proxy.
  struct sockaddr_in server;	// estructura para el servidor
  struct sockaddr_in client;	// estructura para el cliente
  struct sockaddr_in proxy;	// estructura para el proxy (que se ingresa en linea de comandos)

  fd_set readfds, readfdsCopy;	// listas (conjunto) de descriptores de archivos
  int n,i, clientlen, Ret;	// indicador de select, contador, tamaño de estructura client, retorno de read.
  int ClientList[64], ProxyList[64], lastSockC=0, lastSockP=0;	// arreglos de conexiones, posiciones de ultimas conexiones
  // int TotalbytesTX=0, Nconex=0, NTotalconex=0;	// indicadores de bytes TX y conexiones
  char enter[10];	// almacenaje de entrada estandar

  if (argc==4)
  {
		      /*
		       *      * Create a pipe.
		       *      */
		         if (pipe(pfd) < 0) 
		     {
			
			        perror("pipe");
			        exit(1);
		     }
		     
		     
		         /*
			  *      * Create a child process.
			  *      */
		         if ((pid = fork()) < 0) 
		     {
			
			        perror("fork");
			        exit(1);
		     }
		     
		     
		         /*
			  *      * The child process executes "matlab" with -nodesktop option.
			  *      */
		         if (pid == 0) 
		     {
			
			        /*
				 *          * Attach standard input of this child process to read from the pipe.
				 *          */
			        dup2(pfd[0], 0);
			        close(pfd[1]);  /* close the write end of the pipe */
			
			        execlp("scilab", "scilab", "-nw", 0);
			        perror("exec");
			        exit(-1);
		     }
		     
		     
		         /*
			  *      * We won't be reading from the pipe.
			  *      */
		         close(pfd[0]);
		         sd = fdopen(pfd[1], "w");  /* to use fprintf instead of just write */
		     
    ////// inicio obtencion de informacion de maquinas (proxy y server) //////////

    /////////////// server ////////////

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY); 	// se le asigna una direccion disponible al server
    server.sin_port = htons(atoi(argv[1])); 		// puerto del server en 1º argumento

    ////////////// proxy //////////////////

    proxy.sin_family = AF_INET;
    proxy.sin_addr.s_addr = inet_addr(argv[2]);	// direccion del proxy en 2º argumento
    proxy.sin_port = htons(atoi(argv[3])); 	// puerto del proxy en 3º argumento

    //////////// Creacion socket para cliente "sc" ////////////////

    sc = socket (AF_INET,SOCK_STREAM,0);
    if (sc < 0)
    {
      perror("opening stream socket");
      exit(-1);
    }
    reusePort(sc);


    /////////// Bindeado del socket "sc" //////////////

    if ( bind( sc, (struct sockaddr *)&server, sizeof(server) ) )/*
								se asigna la estructura "server" (con todos
								sus datos) al socket "sc"
								*/
    {
      close(sc);	// el socket "sc" se cierra si no se puede realizar el BIND
      perror("binding name to stream socket");
      exit(-1);
    }
    
    // ------------- Señal Signal ----------- //
    signal (SIGALRM, alarm_cb);
    signal (SIGINT, interrupt);

    ///////////// Listening ////////////////////

    listen(sc,4);	// puede dejar a 4 en la lista de espera (cola)

    //////////////////////////////////////////

    clientlen = sizeof(client);	// tamaño de la estructura "client"
    FD_ZERO(&readfdsCopy);	// limpia el conjunto "readfdsCopy"
    FD_SET(sc,&readfdsCopy);	// añade el descriptor dado "sc" al conjunto "readfdsCopy"
    FD_SET(0,&readfdsCopy);		// añade la entrada estandar al conjunto "readfdsCopy"

    printf("\n************************************************************************\n");
    printf("*                       Inicio de Servidor MTB.                        *\n");
    printf("************************************************************************\n");
    printf("                  Corriendo en IP:172.16.0.4   Puerto:%d\n",ntohs(server.sin_port));
    printf("                  Conectado a  IP:%s   Puerto:%d\n",inet_ntoa(proxy.sin_addr),ntohs(proxy.sin_port));
    printf("************************************************************************\n");


    /////////////// Ciclo de atencion de socket //////////////////////

    for(;;)
    {
      alarm(1);  // llama a la alarma cada 1 segundo
      memcpy(&readfds, &readfdsCopy, sizeof(fd_set));	// copia area de memoria de "readfdsCopy" a "readfds"
      n = select(FD_SETSIZE, &readfds, (fd_set *) 0, (fd_set *) 0, NULL);/*
      									creo el select
									se esperan caract del listado "readfds"
									para lectura no se esperan listas de
									escritura ni de excepcion.
									*/
      if (n > 0)
      {
        if (FD_ISSET(sc, &readfds))	/* mira a ver si un descriptor del socket "sc"
					esta activado en la lista "readfds"*/
	{
	  //////////// Conexion del cliente (alimentador) con servidor ///////////////

	  ClientList[lastSockC++] = accept(sc, (struct sockaddr *)&client, &clientlen);/*
	  										 se escribe en arreglo
											 "Clientlist", en
											 posicion "lastSockC",
											 el nuevo descriptor de
											 archivo entregado por
											 accept, del socket "sc",
											 que esta pidiendo
											 atencion. Mientras se
											 realiza la coneccion
											 con el cliente y se
											 guarda la info del
											 cliente en la estructura
											 "client"
											 */
	  FD_SET(ClientList[lastSockC-1], &readfdsCopy);

	  ///////////// Conexion del servidor (ppw) con proxy ////////////////

	  //////////// Creacion socket para el proxy "sp" ////////////////

	  sp = socket (AF_INET,SOCK_STREAM,0);
	  if (sp < 0)
    	  {
	    perror("opening stream socket");
      	    exit(-1);
    	  }

	  reusePort(sp);

	  //////////// Conectando de ppw al proxy ///////////////////

	  ProxyList[lastSockP++] = sp;	// se asiga el socket "sp" al arreglo de conexiones con el proxy
	  FD_SET(ProxyList[lastSockP-1], &readfdsCopy);

	  if ( connect(sp, (struct sockaddr *)&proxy, sizeof(proxy)) < 0 )
	  {
	    close(sp);
	    perror("connecting stream socket");
	    exit(0);
	  }
	  Nconex++;
	}

        ////////// Ciclo de manejo de requerimentos cliente - proxy ///////////

	for (i=0; i < lastSockC; i++)
	{
	  if (FD_ISSET(ClientList[i], &readfds))	/* mira a ver si el descriptor "ClientList[i]"
							esta activado en la lista "readfds"*/

	    if (SocketConnector(ClientList[i],ProxyList[i],0) < 0)	/*
	    								entrada a funcion "SocketConnector"
									para leer requerimientos del cliente
									y enviarselos al proxy.
	    								*/
	    {
	      FD_CLR(ClientList[i], &readfdsCopy);	// borra el descriptor "ClientList[i]" de "readfdsCopy"
	      FD_CLR(ProxyList[i], &readfdsCopy);	// borra el descriptor "ProxyList[i]" de "readfdsCopy"
	      ClientList[i] = ClientList[--lastSockC];	// shift para descartar el socket que se desconectó
	      ProxyList[i] = ProxyList[--lastSockP];	// de "ClientList" y "ProxyList"
	      i--;
	    }
	  if (FD_ISSET(ProxyList[i], &readfds))		/* mira a ver si el descriptor "ProxyList[i]"
							esta activado en la lista "readfds" */
	    if (SocketConnector(ProxyList[i],ClientList[i],1) < 0)	/*
	    								entrada a funcion "SocketConnector"
									para leer los datos enviados por proxy
									(requeridos por cliente) para enviarselos
									finalmete al cliente.
	    								*/
	    {
	      FD_CLR(ClientList[i], &readfdsCopy);	// borra el descriptor "ClientList[i]" de "readfdsCopy"
	      FD_CLR(ProxyList[i], &readfdsCopy);	// borra el descriptor "ProxyList[i]" de "readfdsCopy"
	      ClientList[i] = ClientList[--lastSockC];	// shift para descartar el socket que se desconectó
	      ProxyList[i] = ProxyList[--lastSockP];	// de "ClientList" y "ProxyList"
	      i--;
	    }
        }
	//if (FD_ISSET(0, &readfds))	// revisa si la entrada estandar esta activa en la lista "readfds"
	//{
	/*  if(alarmcondition == 1)//(Ret=read(0, enter, sizeof(enter)))>0)	// se asegura que se ha ingresado un "Enter" (retorno de carro)
	  {
	    TotalbytesTX=TotalbytesTX+bytesTX;	// Guarda el total de bytes transmitidos hacia el cliente
	    NTotalconex=NTotalconex+Nconex;		// Guarda el total de conexiones hechas (cliente o proxy)
	    system("date");
	    printf("Bytes TX \tNuevas Conexiones\tTotal Bytes TX\tTotal Conexiones\n");	
	    printf("%d\t\t\t%d\t\t\t%d\t\t%d\n",bytesTX,Nconex,TotalbytesTX,NTotalconex);
	    bytesTX=0;	// resetea las variables de conexion y bytes cada vez que se presiona "Enter" (retorno de carro)
	    Nconex=0;
          alarmcondition = 0;
	  }*/
	//}  // xbasc para scilab (no hold on)
      }
    }
  }
  else
  {
    printf("Error de sintasis:\n\n");
    printf("              Sintaxis: ./ppw <Puerto_Local> <IP_Proxy> <Puerto_Proxy>\n\n");
  }
}

//////////////////////// Funcion utilizada para realizar la conexion cliente-proxy /////////////////////

int SocketConnector(int lector, int escritor, int enable)
{
  char buf[512];
  int rc;

  for(;;)
  {
    if( (rc=read(lector, buf, sizeof(buf))) < 0)	/* lee la informacion proveniente del descriptor "lector"
    						 	y loguarda en "buf" (cliente o proxy)*/
      perror("receiving stream  message");
    if (rc > 0)
    {
      if (send(escritor, buf, rc, 0) <0 )	/* manda la informacion guardada en "buf" al
      						descriptor "escritor" (cliente o proxy)*/
        perror("sending stream message");
      if(enable==1)	// se asegura que los bytes analizados son los de trafico de bajada (de proxy a cliente)
      {
	bytesTX=bytesTX+rc;
      }
	else
	bytesRX=bytesRX+rc;

      return(1);
    }
    else
    {
      close (lector);	// cierra descriptor de archivos "lector"
      close (escritor);	// cierra descriptor de archivos "escritor"
      return(-1);
    }
  }
}

//////////// Funcion para reusar el socket //////////////////

void reusePort(int s)
{
  int one=1;

  if ( setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char *) &one,sizeof(one)) == -1 )
  {
    printf("error in setsockopt,SO_REUSEPORT \n");
    exit(-1);
  }
}

////////////// Funcion Alarma /////////////////////


void alarm_cb (int signum)
{
   int i;
        
	        TotalbytesTX = TotalbytesTX + bytesTX;	// Guarda el total de bytes transmitidos hacia el cliente
	        TotalbytesRX = TotalbytesRX + bytesRX;	// Guarda el total de bytes recibidos hacia el cliente
	        NTotalconex=NTotalconex+Nconex;		// Guarda el total de conexiones hechas (cliente o proxy)
       	        system("date");
	        printf("Bytes TX\tBytes RX\tNuevas Conexiones\tTotal Bytes TX\tTotal Bytes RX\tTotal Conexiones\n");	
	        printf("%d\t\t%d\t\t%d\t\t\t%d\t\t%d\t\t%d\n",bytesTX,bytesRX,Nconex,TotalbytesTX,TotalbytesRX,NTotalconex);
	        
              datosRX[alarmtimer%120].tiempo = alarmtimer;   
              datosRX[alarmtimer%120].data = bytesRX/1000;
              datosTX[alarmtimer%120].tiempo = alarmtimer;
              datosTX[alarmtimer%120].data = bytesTX/1000;
              datosConex[alarmtimer%120].tiempo = alarmtimer;
              datosConex[alarmtimer%120].data = Nconex;
    alarmtimer++;
      if (alarmtimer%119 == 0) 
      {
	      // ingreso matriz
              fprintf(sd, "datos = [");
              for (i=0; i<110; i++)
	   {     
                   fprintf(sd, "%d %d %d %d;", datosRX[i].tiempo, datosRX[i].data, datosTX[i].data, datosConex[i].data);// #tiempo #datoTX #datoRX #nconex
	   }
	      fprintf(sd, "];\n");fflush(sd);
	      fprintf(sd, "xclear();\n");fflush(sd);
	      fprintf(sd, "xset('font',2,2);\n");	 
              fprintf(sd, "subplot(3,1,1);\n");
              fprintf(sd, "xtitle('Monitor de tráfico de bajada','Tiempo [s]','RX [kbps]');\n");
	      fprintf(sd, "xgrid();\n");
	      fprintf(sd, "plot2d(datos(:,1),datos(:,2));\n");fflush(sd);
              fprintf(sd, "subplot(3,1,2);\n");
              fprintf(sd, "xtitle('Monitor de tráfico de subida','Tiempo [s]','TX [kbps]');\n");
	      fprintf(sd, "xgrid();\n");
	      fprintf(sd, "plot2d(datos(:,1),datos(:,3));\n");fflush(sd);
	      fprintf(sd, "subplot(3,1,3);\n");
	      fprintf(sd, "xtitle('Monitor de número de conexiones','Tiempo [s]','Conexiones ');\n");
	      fprintf(sd, "xgrid();\n");
	      fprintf(sd, "plot2d(datos(:,1),datos(:,4));\n");fflush(sd);
	      //fprintf(sd, "clear;\n");			    
              //fprintf(sd, "datos = [");
	      //for (i=60; i<119; i++)
	   //{  
	              // fprintf(sd, "%d %d %d %d;", datosRX[i].tiempo, datosRX[i].data, datosTX[i].data, datosConex[i].data);// #$
	  // }
	               
	               //fprintf(sd, "];\n");fflush(sd);
	               //fprintf(sd, "xset('font',2,2);\n");
	               //fprintf(sd, "subplot(3,1,1);\n");
	               //fprintf(sd, "xtitle('Monitor de tráfico de bajada','Tiempo [s]','RX [kbps]');\n");
	               //fprintf(sd, "xgrid();\n");
	               //fprintf(sd, "plot2d(datos(:,1),datos(:,2));\n");fflush(sd);
	               //fprintf(sd, "subplot(3,1,2);\n");
	               //fprintf(sd, "xtitle('Monitor de tráfico de subida','Tiempo [s]','TX [kbps]');\n");
	               //fprintf(sd, "xgrid();\n");
	               //fprintf(sd, "plot2d(datos(:,1),datos(:,3));\n");fflush(sd);
	               //fprintf(sd, "subplot(3,1,3);\n");
	               //fprintf(sd, "xtitle('Monitor de número de conexiones','Tiempo [s]','Conexiones ');\n");
	               //fprintf(sd, "xgrid();\n");
	               //fprintf(sd, "plot2d(datos(:,1),datos(:,4));\n");fflush(sd);
	 
              	 
      }
          
              bytesTX=0;	// resetea las variables de conexion y bytes cada vez que se presiona "Enter" (retorno de carro)
	      bytesRX=0;      // resetea las variables de conexion y bytes cada vez que se presiona "Enter" (retorno de carro)
	      Nconex=0;           
	   //  }
        return;
}


void interrupt (int signum)
     {
	
	
	   fprintf(sd, "\n exit\n"); fflush(sd);
	   waitpid(pid, &status, 0);
	       fclose(sd);
	   exit(0);
     }
   
   







