/* Socks Server 5
 * Copyright (C) 2002 - 2006 by Matteo Ricchetti - <matteo.ricchetti@libero.it>

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "SS5Main.h"
#include "SS5Mod_balance.h"


S5RetCode InitModule( struct _module *m )
{
  m->AddVip  = AddVip;
  m->AddConn = S5AddConn2Real;
  m->RemoveConn = S5RemoveConn2Real;
  m->LoadBalancing = LoadBalancing;
  m->Balancing = Balancing;
  m->FreeConnectionTable = FreeConnectionTable;
  m->FreeAffinity = FreeAffinity;

  return OK;
}

S5RetCode AddVip (char *real, unsigned int vid, unsigned int idx)
{
  S5AddReal2ConnectionTable(real, vid, idx);
 
  return OK;
}

S5RetCode FreeConnectionTable (struct _S5ConnectionEntry *ce)
{
  free(ce);
 
  return OK;
}

S5RetCode LoadBalancing( struct _SS5ClientInfo *ci, struct _SS5RequestInfo *ri )
{
  struct in_addr s;
  struct in_addr d;

  unsigned int vid;
  unsigned int ttl_status;

  /*
   *    If affinity enabled, looks for affinity between src ip and dst ip
   *    before using balancing
   */
  if( SS5SocksOpt.Sticky ) {
    IFLINUX( inet_aton(ci->SrcAddr,&s); )
    IFSOLARIS( inet_pton(AF_INET, ci->SrcAddr, &s); )

    ttl_status = OK;
    vid = S5GetRealVid(ri->DstAddr);

    LOCKMUTEXCA()
    if( (d.s_addr = S5GetAffinity(s.s_addr,&ttl_status,vid)) == ERR ) {
      if( ttl_status == ERR ) {
        /*
         *    If age expired, remove affinity between src ip and dst ip
         */
        S5RemoveAffinity(s.s_addr,vid);
      }

      /*
       *    Balances with least connections
       */
      S5LeastConnectionReal(ri->DstAddr);

      /*
       *    Setup affinity between src ip and dst ip
       */
      IFSOLARIS( inet_pton(AF_INET, ri->DstAddr, &d); )
      IFLINUX( inet_aton((const char *)ri->DstAddr,&d); )

      S5SetAffinity(s.s_addr,d.s_addr,vid);
    }
    else {
      strncpy(ri->DstAddr,inet_ntoa(d),sizeof(ri->DstAddr));
    }
    UNLOCKMUTEXCA()
  }
  else {
    /*
     *    Balances with least connections
     */
    S5LeastConnectionReal(ri->DstAddr);
  }
  
  return OK;
}

S5RetCode S5LeastConnectionReal(char *s5application)
{
  register unsigned int i;
  register unsigned int j;

  unsigned int id1;
  unsigned int vid;
  unsigned int conn;

  LOCKMUTEXCT()
  /*
   *    Search for one real with the smaller number of connections
   */
  for(i = 0; i < NReal; i++) {
    if( STREQ(S5ConnectionTable.S5ConnectionEntry[i]->Real,s5application,strlen(s5application)) ) {
      id1  = i;
      vid  = S5ConnectionTable.S5ConnectionEntry[i]->Vid; 
      conn = S5ConnectionTable.S5ConnectionEntry[i]->Connection;

      for(j = 0; j<NReal; j++)
        if( S5ConnectionTable.S5ConnectionEntry[j]->Vid == vid )
	  if(S5ConnectionTable.S5ConnectionEntry[j]->Connection < conn ) {
            conn = S5ConnectionTable.S5ConnectionEntry[j]->Connection;
	    id1  = j;
	  }
      strncpy(s5application,S5ConnectionTable.S5ConnectionEntry[id1]->Real,sizeof(S5ConnectionTable.S5ConnectionEntry[id1]->Real) - 1);
      UNLOCKMUTEXCT()
      return OK;
    }
  }
  UNLOCKMUTEXCT()
  return ERR;
}

S5RetCode S5AddReal2ConnectionTable(char *real, unsigned int vid, unsigned int idx)
{
  S5ConnectionTable._tmp_S5ConnectionEntry[idx] = (struct _S5ConnectionEntry *)calloc(1,sizeof(struct _S5ConnectionEntry));

  strncpy(S5ConnectionTable._tmp_S5ConnectionEntry[idx]->Real,real,strlen(real));
  S5ConnectionTable._tmp_S5ConnectionEntry[idx]->Vid = vid;
  S5ConnectionTable._tmp_S5ConnectionEntry[idx]->Connection = 0;

  return OK;
}

S5RetCode S5GetRealVid(char *real)
{
  register unsigned int idx;

  for(idx = 0; idx < NReal; idx++)
    if( STREQ(S5ConnectionTable.S5ConnectionEntry[idx]->Real,real,strlen(real)) ) {
      return S5ConnectionTable.S5ConnectionEntry[idx]->Vid;
    }
  return ERR;
}

S5RetCode S5AddConn2Real(char *real)
{
  register unsigned int idx;

  for(idx = 0; idx < NReal; idx++)
    if( STREQ(S5ConnectionTable.S5ConnectionEntry[idx]->Real,real,strlen(real)) ) {
      LOCKMUTEXCT()
      S5ConnectionTable.S5ConnectionEntry[idx]->Connection++;
      UNLOCKMUTEXCT()
      return OK;
    }
  return ERR;
}

S5RetCode S5RemoveConn2Real(char *real)
{
  register unsigned int idx;

  for(idx = 0; idx < NReal; idx++)
    if( STREQ(S5ConnectionTable.S5ConnectionEntry[idx]->Real,real,strlen(real)) ) {
      if( S5ConnectionTable.S5ConnectionEntry[idx]->Connection ) {
        LOCKMUTEXCT()
        S5ConnectionTable.S5ConnectionEntry[idx]->Connection--;
        UNLOCKMUTEXCT()
      }
      return OK;
    }
  return ERR;
}

inline S5Limit S5StickyHash( unsigned long int srcip )
{
  return (srcip % MAXSTICKYLIST);
}

unsigned long int S5GetAffinity(unsigned long int srcip, unsigned int *ttl_status, unsigned int vid)
{
  S5Limit idx;

  struct _S5StickyNode *node;

  idx = S5StickyHash( srcip );

  if( S5StickyList[idx] == NULL )
    return ERR;
  else {
    node = S5StickyList[idx];

    do {
     if( (node->srcip == srcip)  && (node->vid == vid)) {
       if( node->ttl > time(NULL) ) {
         return node->dstip;
       }
       else {
	 *ttl_status = ERR;
         return ERR;
       }
     }  
     node = node->next;
    } while(node != NULL );
  }
  return ERR;
}

S5RetCode S5SetAffinity(unsigned long int srcip, unsigned long int dstip, unsigned int vid )
{
  int idx;
  struct _S5StickyNode *node;
  struct in_addr s,d;

  s.s_addr = srcip;
  d.s_addr = dstip;

  idx=S5StickyHash( srcip );

  if( S5StickyList[idx]== NULL ) {
    S5StickyList[idx] = (struct _S5StickyNode *)calloc(1,sizeof(struct _S5StickyNode));
    S5StickyList[idx]->srcip = srcip;
    S5StickyList[idx]->dstip = dstip;
    S5StickyList[idx]->ttl = (time(NULL) + SS5SocksOpt.StickyAge);
    S5StickyList[idx]->vid = vid;
  }
  else {
    node=S5StickyList[idx];
    while( node->next != NULL ){
      node=node->next;
    }
    node->next = (struct _S5StickyNode *)calloc(1,sizeof(struct _S5StickyNode));
    node->next->srcip = srcip;
    node->next->dstip = dstip;
    node->next->ttl = (time(NULL) + SS5SocksOpt.StickyAge);
    node->next->vid = vid;
    node->next->next = NULL;
  }
  return OK;
}

S5RetCode S5RemoveAffinity(unsigned long int srcip, unsigned int vid)
{
  int idx;
  struct _S5StickyNode *node;
  struct _S5StickyNode **backnode;

  idx = S5StickyHash( srcip );

  node = S5StickyList[idx];
  backnode = &S5StickyList[idx];

  do {
   if( (node->srcip == srcip) && (node->vid == vid) ) {
     if( node->next != NULL )
       *backnode = node->next;
     else
       *backnode = NULL;

     free(node);
     break;
   }  
   backnode = &node->next;
   node = node->next;

  } while(node != NULL );

  return OK;
}

S5RetCode Balancing( struct _SS5ClientInfo *ci, struct _SS5Socks5Data *sd )
{

  register unsigned int idx;

  struct in_addr s;
  struct in_addr d;

  time_t currentAge;

  struct _S5StickyNode *node;

  char *buf,
       sa[16],
       da[16];

  buf = (char *)calloc(512,sizeof(char));

  if( STREQ(sd->MethodRequest,"GET /balancing HTTP/1.",sizeof("GET /balancing HTTP/1.") - 1) ) {
    /*
     *     Create response
     */

    for(idx = 0; idx < NReal; idx++) {
      snprintf(buf,512 - 1, "%s\n%u\n%u\n",
        S5ConnectionTable.S5ConnectionEntry[idx]->Real,
        S5ConnectionTable.S5ConnectionEntry[idx]->Vid,
        S5ConnectionTable.S5ConnectionEntry[idx]->Connection);

      /*
       *    Send http response
       */
      if( send(ci->Socket,buf,512,SS5_SEND_OPT) == -1) {
        free(buf);
        return ERR;
      }
    }

    fcntl(ci->Socket,F_SETFL,O_NONBLOCK);
    recv(ci->Socket,buf,strlen(buf),0);
    free(buf);
    return OK;
  }
  else if( STREQ(sd->MethodRequest,"GET /sticky HTTP/1.",sizeof("GET /sticky HTTP/1.") - 1) ) {
    /*
     *     Create response
     */

    for(idx = 0; idx < 997; idx++) {
      node = S5StickyList[idx];

      while( node != NULL ) {

        s.s_addr = node->srcip;
        d.s_addr = node->dstip;

        strncpy(sa,inet_ntoa(s),sizeof(sa));
        strncpy(da,inet_ntoa(d),sizeof(da));

        currentAge = time(NULL);

        snprintf(buf,512 - 1, "%s\n%u\n%s\n%lu\n%lu\n",sa,node->vid,da,node->ttl,currentAge);
        /*
         *    Send http response
         */
        if( send(ci->Socket,buf,512,SS5_SEND_OPT) == -1) {
          free(buf);
          return ERR;
        }

        node = node->next;
      }

    }
    fcntl(ci->Socket,F_SETFL,O_NONBLOCK);
    recv(ci->Socket,buf,strlen(buf),0);
    free(buf);
    return OK;
  }

  return ERR;
}

S5RetCode FreeAffinity( struct _S5StickyNode **node )
{
  struct _S5StickyNode *lnode;
  struct _S5StickyNode *lnode_prev=NULL;

  lnode=*node;

  if( lnode != NULL ) {
    do {
      while( lnode->next != NULL ) {
        lnode_prev=lnode;
        lnode=lnode->next;
      }
      free(lnode);
      if( lnode_prev != NULL ) {
        lnode_prev->next=NULL;
        lnode=lnode_prev;
        lnode_prev=NULL;
      }
      else
        lnode=NULL;
    } while( (lnode) != NULL );
  }
  *node=NULL;
  
  return OK;

}



syntax highlighted by Code2HTML, v. 0.9.1