/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: natdata.c$
* $Version : 3.5.2.0$
* $Date    : Jan-15-2010$
*
* Comments:
*
*   This file contains the NAT data strucutre interface functions.
*
*END************************************************************************/

#include <rtcsrtos.h>
#include <rtcs.h>
#include <rtcs_prv.h>

#if RTCSCFG_ENABLE_NAT

#include "nat.h"
#include "nat_prv.h"


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : NAT_insert
* Returned Value  : NAT_SESSION_STRUCT_PTR or NULL if error
* Comments        :
*        Creates a new session. Will create a new session for ICMP error
*     messages, but won't link them into the IP_RADIX trees.
*
*END*-----------------------------------------------------------------*/

static void NAT_insert_find_unused
   (
      pointer _PTR_  node_ptr_ptr,
      pointer        data_ptr
   )
{ /* Body */
    boolean _PTR_    found_ptr = data_ptr;

   /* TRUE if there is something there, FALSE if not */
   *found_ptr = (*node_ptr_ptr != NULL);

} /* Endbody */



static void NAT_insert_insert
   (
      pointer _PTR_  node_ptr_ptr,
      pointer        data_ptr
   )
{ /* Body */
   IPRADIX_NODE_PTR        node_ptr = (pointer) node_ptr_ptr;
   NAT_SESSION_STRUCT_PTR  nat_session_ptr = data_ptr;

   /* Link according to incoming or outgoing */
   if ((nat_session_ptr->STATE & NAT_STATE_INSERT_IN) == NAT_STATE_INSERT_IN) {
      nat_session_ptr->NEXT_IN = node_ptr->DATA;
   } else {
      nat_session_ptr->NEXT_OUT = node_ptr->DATA;
   } /* Endif */

   nat_session_ptr->STATE &= ~NAT_STATE_INS_ERR;
   node_ptr->DATA = nat_session_ptr;

} /* Endbody */


NAT_SESSION_STRUCT_PTR NAT_insert
   (
      IP_HEADER_PTR     ip_header_ptr, /* [IN] Pointer to the IP datagram */
      uint_32_ptr       error_ptr      /* [OUT] Reason for returning NULL */
   )
{ /* Body */
   NAT_CFG_STRUCT_PTR      nat_cfg_ptr = RTCS_getcfg(NAT);
   NAT_SESSION_STRUCT_PTR  new_nat_session_ptr;
   uint_16_ptr             last_used_port_ptr;
   TRANSPORT_UNION         transport;
   uint_32                 key;
   uint_16                 icmp_type, port;
   boolean                 used = FALSE;
   boolean                 route_exists;
   
   /* Get a pointer to the upper level protocol */
   transport.PTR = TRANSPORT_PTR(ip_header_ptr);

   /* Allocate memory */
   new_nat_session_ptr = RTCS_part_alloc_zero(nat_cfg_ptr->SESSION_PART);

   if (new_nat_session_ptr==NULL) {
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endid */

   /* Setup event information */
   new_nat_session_ptr->TIMEOUT.SESSION_PTR = new_nat_session_ptr;

   /* Copy information (works for all protocols) */
   new_nat_session_ptr->PRV_HST = ntohl(ip_header_ptr->SOURCE);
	new_nat_session_ptr->PUB_HST = ntohl(ip_header_ptr->DEST);

   /* Choose a NAT host IP. */
   new_nat_session_ptr->NAT_HST = IP_route_find(new_nat_session_ptr->PUB_HST, 0);
   route_exists = new_nat_session_ptr->NAT_HST != INADDR_ANY;

   new_nat_session_ptr->IP_PROT = ntohc(ip_header_ptr->PROTOCOL);
   new_nat_session_ptr->SNAT_OR_DNAT = SNAT;


   /* No route to host available */
   if (!route_exists) {
      RTCS_part_free(new_nat_session_ptr);
      *error_ptr = RTCSERR_IP_UNREACH;
      return NULL;
   } /* Endif */

   /* Do protocol specific initialization */
   switch (new_nat_session_ptr->IP_PROT) {
      case IPPROTO_ICMP:
         icmp_type = ntohc(transport.ICMP_PTR->TYPE);
         if (icmp_type == ICMPTYPE_ECHO_REQ     ||
             icmp_type == ICMPTYPE_TIME_REQ     ||
             icmp_type == ICMPTYPE_INFO_REQ     ||
             icmp_type == ICMPTYPE_AM_REQ)
         {
            new_nat_session_ptr->PRV_PORT = ntohs(transport.ECHO_PTR->SEQ);		
            last_used_port_ptr = &nat_cfg_ptr->ICMP_ID;
         } else {
            if (ICMPTYPE_ISQUERY(icmp_type)) {
               /* This means a reply without a request. Should not happen */
               RTCS_part_free(new_nat_session_ptr);
               *error_ptr = RTCSERR_NAT_UNEXPECTED;
               new_nat_session_ptr = NULL;
            } else {
               /* We will be deleting the session, so don't store it */
               *error_ptr = RTCS_OK;
            } /* Endif*/
            return new_nat_session_ptr;
         } /* Endif */

         new_nat_session_ptr->TIMEOUT.TIME = nat_cfg_ptr->TIMEOUT_ICMP;
         break;

      case IPPROTO_TCP:
         new_nat_session_ptr->PRV_PORT = ntohs(transport.TCP_PTR->source_port);
         new_nat_session_ptr->PUB_PORT = ntohs(transport.TCP_PTR->dest_port);         	
         last_used_port_ptr = &nat_cfg_ptr->TCP_PORT;
         new_nat_session_ptr->TIMEOUT.TIME = nat_cfg_ptr->TIMEOUT_TCP;
         break;

      case IPPROTO_UDP:
         new_nat_session_ptr->PRV_PORT = ntohs(transport.UDP_PTR->SRC_PORT);
         new_nat_session_ptr->PUB_PORT = ntohs(transport.UDP_PTR->DEST_PORT);
         last_used_port_ptr = &nat_cfg_ptr->UDP_PORT;
         new_nat_session_ptr->TIMEOUT.TIME = nat_cfg_ptr->TIMEOUT_UDP;
         break;

      default:    /* Should never get here */
         RTCS_part_free(new_nat_session_ptr);
         // CR-TBD no error returned
         *error_ptr = RTCSERR_NAT_UNEXPECTED;
         return NULL;
   } /* Enswitch */


   /*
   ** Reserve a port or query id.
   ** Take the last used number from the last_used_num pointer, and
   ** decrement by 1. Search radix tree to see if used, and
   ** continue until an unused number is found. When the #define
   ** minimum NAT port is reached wrap up to the maximum port.
   ** If we return to the *last_used_num, they are
   ** all reserved, so we free the session and return NULL.
   */   
   
   port = NAT_NEXT_PORT(nat_cfg_ptr,*last_used_port_ptr);

   while (port != *last_used_port_ptr) {

      /* Search tree for port/id number */
      key = NAT_KEY(new_nat_session_ptr->IP_PROT,port);
      IPRADIX_insert(&nat_cfg_ptr->ROOT_IN, key, 0xFFFFFFFF, 0, NAT_insert_find_unused, &used);

      if (used == FALSE) {          /* We found a free port/id number */
            new_nat_session_ptr->NAT_PORT = port;
         *last_used_port_ptr = port;
      } else {                      /* Port/id is used, keep looking */
         used = FALSE;
         port=NAT_NEXT_PORT(nat_cfg_ptr,port);
      }/* Endif */
   }/* Endwhile */
   
      if (port != new_nat_session_ptr->NAT_PORT) {
         *error_ptr = RTCSERR_NAT_NO_SESSION;
         RTCS_part_free(new_nat_session_ptr);
      return NULL;
   }/* Endif */

   /* Update STATS */
   nat_cfg_ptr->STATS.ST_SESSIONS++;
   nat_cfg_ptr->STATS.ST_SESSIONS_SNAT++;
   nat_cfg_ptr->STATS.ST_SESSIONS_OPEN++;
   if (nat_cfg_ptr->STATS.ST_SESSIONS_OPEN > nat_cfg_ptr->STATS.ST_SESSIONS_OPEN_MAX) {
      nat_cfg_ptr->STATS.ST_SESSIONS_OPEN_MAX = nat_cfg_ptr->STATS.ST_SESSIONS_OPEN;
   } /* Endif */

   /*
   ** Call IPRADIX_insert on each radix tree. Pass in a function that will
   ** link the session struct onto a linked list starting at the node.
   ** If last_used_port_ptr == NULL, we are routing a one time ICMP message and
   ** there is no need to save the session.
   */
   /* Insert into incoming packet tree */
   new_nat_session_ptr->STATE = NAT_STATE_INSERT_IN | NAT_STATE_INS_ERR;
   IPRADIX_insert(&nat_cfg_ptr->ROOT_IN, key, 0xFFFFFFFF, nat_cfg_ptr->RADIX_IN,
      NAT_insert_insert, new_nat_session_ptr);

   /* If the insert failed, we free the session struct */
   if (new_nat_session_ptr->STATE & NAT_STATE_INS_ERR) {
      NAT_delete(new_nat_session_ptr);
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endif */

   /* insert into outgoing packet tree */
   key = NAT_KEY(new_nat_session_ptr->IP_PROT,new_nat_session_ptr->PRV_PORT);
   new_nat_session_ptr->STATE = NAT_STATE_INSERT_OUT | NAT_STATE_INS_ERR;;

   IPRADIX_insert(&nat_cfg_ptr->ROOT_OUT, key, 0xFFFFFFFF, nat_cfg_ptr->RADIX_OUT,
      NAT_insert_insert, new_nat_session_ptr);

   /* If the insert failed, we free the session struct */
   if (new_nat_session_ptr->STATE & NAT_STATE_INS_ERR) {
      NAT_delete(new_nat_session_ptr);
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endif */

   /* Return a pointer to the new structure */
   return new_nat_session_ptr;

} /* Endbody */


NAT_SESSION_STRUCT_PTR NAT_insert_dnat
   (
      IP_HEADER_PTR        ip_header_ptr, /* [IN] Pointer to the IP datagram */
      DNAT_RULE_STRUCT_PTR dnat_rule_ptr,
      uint_32_ptr          error_ptr      /* [OUT] Reason for returning NULL */
   )
{ /* Body */
   NAT_CFG_STRUCT_PTR      nat_cfg_ptr = RTCS_getcfg(NAT);
   NAT_SESSION_STRUCT_PTR  new_nat_session_ptr;
   TRANSPORT_UNION         transport;
   uint_32                 key;
   boolean                 used = FALSE;

   /* Get a pointer to the upper level protocol */
   transport.PTR = TRANSPORT_PTR(ip_header_ptr);

   /* Allocate memory */
   new_nat_session_ptr = RTCS_part_alloc_zero(nat_cfg_ptr->SESSION_PART);

   if (new_nat_session_ptr==NULL) {
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endid */

   /* Setup event information */
   new_nat_session_ptr->TIMEOUT.SESSION_PTR = new_nat_session_ptr;

   /* Copy information (works for all protocols) */
   new_nat_session_ptr->PRV_HST = dnat_rule_ptr->PRIVATE_IP; 
   new_nat_session_ptr->PUB_HST = ntohl(ip_header_ptr->SOURCE);
   new_nat_session_ptr->NAT_HST = ntohl(ip_header_ptr->DEST);
   new_nat_session_ptr->IP_PROT = dnat_rule_ptr->IP_PROTOCOL;
   new_nat_session_ptr->SNAT_OR_DNAT = DNAT;

   /* Do protocol specific initialization */
   switch (new_nat_session_ptr->IP_PROT) {
 
      case IPPROTO_TCP:
         new_nat_session_ptr->PUB_PORT = ntohs(transport.TCP_PTR->source_port);         	
         new_nat_session_ptr->NAT_PORT = ntohs(transport.TCP_PTR->dest_port);
         new_nat_session_ptr->PRV_PORT = new_nat_session_ptr->NAT_PORT - 
            dnat_rule_ptr->PUBLIC_START_PORT + dnat_rule_ptr->PRIVATE_START_PORT;
         if (dnat_rule_ptr->TIMEOUT) {
            new_nat_session_ptr->TIMEOUT.TIME = dnat_rule_ptr->TIMEOUT;
         } else {            
            new_nat_session_ptr->TIMEOUT.TIME = nat_cfg_ptr->TIMEOUT_TCP;
         }
         break;

      case IPPROTO_UDP:
         new_nat_session_ptr->PUB_PORT = ntohs(transport.UDP_PTR->SRC_PORT);
         new_nat_session_ptr->NAT_PORT = ntohs(transport.UDP_PTR->DEST_PORT);
         new_nat_session_ptr->PRV_PORT = new_nat_session_ptr->NAT_PORT -
           dnat_rule_ptr->PUBLIC_START_PORT + dnat_rule_ptr->PRIVATE_START_PORT;
          if (dnat_rule_ptr->TIMEOUT) {
            new_nat_session_ptr->TIMEOUT.TIME = dnat_rule_ptr->TIMEOUT;
         } else {            
            new_nat_session_ptr->TIMEOUT.TIME = nat_cfg_ptr->TIMEOUT_UDP;
         }
         break;

      default:    /* Should never get here */
         RTCS_part_free(new_nat_session_ptr);
         *error_ptr = RTCSERR_NAT_UNEXPECTED;
         return NULL;
   } /* Enswitch */


 

   /* Update STATS */
   nat_cfg_ptr->STATS.ST_SESSIONS++;
   nat_cfg_ptr->STATS.ST_SESSIONS_DNAT++;
   nat_cfg_ptr->STATS.ST_SESSIONS_OPEN++;
   if (nat_cfg_ptr->STATS.ST_SESSIONS_OPEN > nat_cfg_ptr->STATS.ST_SESSIONS_OPEN_MAX) {
      nat_cfg_ptr->STATS.ST_SESSIONS_OPEN_MAX = nat_cfg_ptr->STATS.ST_SESSIONS_OPEN;
   } /* Endif */

   /*
   ** Call IPRADIX_insert on each radix tree. Pass in a function that will
   ** link the session struct onto a linked list starting at the node.
   ** If last_used_port_ptr == NULL, we are routing a one time ICMP message and
   ** there is no need to save the session.
   */
   /* Insert into incoming packet tree */
   key = NAT_KEY(new_nat_session_ptr->IP_PROT,new_nat_session_ptr->NAT_PORT);
   new_nat_session_ptr->STATE = NAT_STATE_INSERT_IN | NAT_STATE_INS_ERR;
   IPRADIX_insert(&nat_cfg_ptr->ROOT_IN, key, 0xFFFFFFFF, nat_cfg_ptr->RADIX_IN,
      NAT_insert_insert, new_nat_session_ptr);

   /* If the insert failed, we free the session struct */
   if (new_nat_session_ptr->STATE & NAT_STATE_INS_ERR) {
      NAT_delete(new_nat_session_ptr);
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endif */

   /* insert into outgoing packet tree */
   key = NAT_KEY(new_nat_session_ptr->IP_PROT,new_nat_session_ptr->PRV_PORT);
   new_nat_session_ptr->STATE = NAT_STATE_INSERT_OUT | NAT_STATE_INS_ERR;;

   IPRADIX_insert(&nat_cfg_ptr->ROOT_OUT, key, 0xFFFFFFFF, nat_cfg_ptr->RADIX_OUT,
      NAT_insert_insert, new_nat_session_ptr);

   /* If the insert failed, we free the session struct */
   if (new_nat_session_ptr->STATE & NAT_STATE_INS_ERR) {
      NAT_delete(new_nat_session_ptr);
      *error_ptr = RTCSERR_OUT_OF_MEMORY;
      return NULL;
   } /* Endif */

   /* Return a pointer to the new structure */
   return new_nat_session_ptr;

} /* Endbody */



/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : NAT_lookup
* Returned Value  : NAT_SESSION_STRUCT_PTR or NULL no session found
* Comments        :
*        Returns a pointer to a session, if it exists.
*
*END*-----------------------------------------------------------------*/

struct nat_lookup {
   NAT_SESSION_STRUCT_PTR  NAT_SESSION_PTR;
   _ip_address             SRC;
   _ip_address             DEST;
   boolean                 TO_PRV_NET;
   uint_16                 PORT;
} ;

static void NAT_lookup_func
   (
      pointer _PTR_  node_ptr_ptr,
      pointer        data_ptr
      
   )
{ /* Body */
   NAT_SESSION_STRUCT_PTR  nat_session_ptr = *node_ptr_ptr;
   struct nat_lookup _PTR_ testdata = data_ptr;
   
   /* Go through linked list of NAT_SESSION_STRUCTs */
   while (nat_session_ptr) {
      if (testdata->TO_PRV_NET) {   
         /* From public network to private network */
         if ((testdata->SRC == nat_session_ptr->PUB_HST) &&
             (testdata->DEST == nat_session_ptr->NAT_HST) && 
             (testdata->PORT == nat_session_ptr->PUB_PORT) ) 
         {
            testdata->NAT_SESSION_PTR = nat_session_ptr;
            break;
         } /* Endif */

         /* Go to next node */
         nat_session_ptr = nat_session_ptr->NEXT_IN;

      } else {                      
         /* From private network to public network */
         if ((testdata->SRC == nat_session_ptr->PRV_HST) &&
             (testdata->DEST == nat_session_ptr->PUB_HST) && 
             (testdata->PORT == nat_session_ptr->PUB_PORT)) 
         {
            testdata->NAT_SESSION_PTR = nat_session_ptr;
            break;
         } /* Endif */

         /* Go to next node */
         nat_session_ptr = nat_session_ptr->NEXT_OUT;

      } /* Endif */
   } /* Endwhile */
} /* Endbody */

static void NAT_lookup_func_lax
   (
      pointer _PTR_  node_ptr_ptr,
      pointer        data_ptr
   )
{ /* Body */
   NAT_SESSION_STRUCT_PTR  nat_session_ptr = *node_ptr_ptr;
   struct nat_lookup _PTR_ testdata = data_ptr;

   /* Go through linked list of NAT_SESSION_STRUCTs */
   while (nat_session_ptr) {
      if (testdata->TO_PRV_NET) {   /* From public network to private network */
         if (testdata->SRC == nat_session_ptr->PUB_HST    &&
             testdata->DEST == nat_session_ptr->NAT_HST)
         {
            testdata->NAT_SESSION_PTR = nat_session_ptr;
            break;
         } /* Endif */

         /* Go to next node */
         nat_session_ptr = nat_session_ptr->NEXT_IN;
      } else {                      /* From private network to public network */
         if (testdata->SRC == nat_session_ptr->PRV_HST    &&
             testdata->DEST == nat_session_ptr->PUB_HST)
         {
            testdata->NAT_SESSION_PTR = nat_session_ptr;
            break;
         } /* Endif */

         /* Go to next node */
         nat_session_ptr = nat_session_ptr->NEXT_OUT;
      } /* Endif */
   } /* Endwhile */
} /* Endbody */


NAT_SESSION_STRUCT_PTR NAT_lookup
   (
      IP_HEADER_PTR ip_header_ptr,  /* [IN] Pointer to the IP datagram */
      boolean       icmp_err,       /* [IN] Is the lookup for an icmp error? */
      boolean       lax,            /* [IN] Non-strict lookup used by ALGs */
      uint_32_ptr	  error_ptr
   )
{ /* Body */
   NAT_CFG_STRUCT_PTR   nat_cfg_ptr = RTCS_getcfg(NAT);
   uint_32              key, ip_protocol = ntohc(ip_header_ptr->PROTOCOL);
   TRANSPORT_UNION     transport;
   struct nat_lookup    data;
   IPRADIX_NODE_PTR     root_ptr;
   boolean              pub_to_prv;
   DNAT_RULE_STRUCT_PTR dnat_rule_ptr;

   /* Get a pointer to the upper level protocol */
   transport.PTR = TRANSPORT_PTR(ip_header_ptr);

   data.NAT_SESSION_PTR = NULL;
   data.PORT = 0;

   if (icmp_err) { /* Do a reverse lookup for ICMP errors */
      data.SRC = ntohl(ip_header_ptr->DEST);
      data.DEST = ntohl(ip_header_ptr->SOURCE);
   } else {
      data.SRC = ntohl(ip_header_ptr->SOURCE);
      data.DEST = ntohl(ip_header_ptr->DEST);
   } /* Endif */

   key = (ip_protocol & 0xFF) << 16;

   /* Find out if the packet is entering or exiting the private network */
   if (NAT_is_private_addr(&nat_cfg_ptr->PRIVATE_NETWORKS,data.SRC)) {
      data.TO_PRV_NET = FALSE;
      root_ptr = &nat_cfg_ptr->ROOT_OUT;
   } else {
      data.TO_PRV_NET = TRUE;
      root_ptr = &nat_cfg_ptr->ROOT_IN;
   } /* Endif */

   /*
   ** If we have received an ICMP error, we are search for the session
   ** corresponding to the embedded header, so we do a reverse lookup
   */
   if (icmp_err) {
      pub_to_prv = data.TO_PRV_NET?FALSE:TRUE;
   } else {
      pub_to_prv = data.TO_PRV_NET;
   } /* Endif */

   if (ip_protocol == IPPROTO_ICMP) {
      key = NAT_KEY(ip_protocol,ntohs(transport.ECHO_PTR->SEQ));
   } else {
      /* If the incoming packet is not an ICMP error, then the source is private */
      if (pub_to_prv) {
         /* It's TCP or UDP, and both have ports in same header position */
         key = NAT_KEY(ip_protocol,ntohs(transport.UDP_PTR->DEST_PORT));
         data.PORT = ntohs(transport.UDP_PTR->SRC_PORT);         	
      } else {
         /* It's TCP or UDP, and both have ports in same header position */
         key = NAT_KEY(ip_protocol,ntohs(transport.UDP_PTR->SRC_PORT));
         data.PORT = ntohs(transport.UDP_PTR->DEST_PORT);         	
      }/* Endif */
   } /* Endif */

   /* Search for an exact match on key */
   if (lax) {
      /* Non strict lookup used by ALGs */
      IPRADIX_insert(root_ptr, key, 0xFFFFFFFF, 0, NAT_lookup_func_lax, &data);
   } else {
      IPRADIX_insert(root_ptr, key, 0xFFFFFFFF, 0, NAT_lookup_func, &data);
   } /* Endif */


   if ((data.NAT_SESSION_PTR==NULL) && pub_to_prv) {
      // Maybe the header matches a DNAT rule
      dnat_rule_ptr = DNAT_lookup_rule(nat_cfg_ptr, ip_header_ptr, pub_to_prv);
      if (dnat_rule_ptr!=NULL) {
         // insert a new DNAT session here   
         data.NAT_SESSION_PTR = NAT_insert_dnat(ip_header_ptr, dnat_rule_ptr, error_ptr );
      }
   }

   return data.NAT_SESSION_PTR;

} /* Endbody */




/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : NAT_delete
* Returned Value  : void
* Comments        :
*        Deletes a NAT session.
*
*END*-----------------------------------------------------------------*/

struct nat_delete {
   _ip_address             SRC;
   _ip_address             DEST;
   boolean                 TO_PRV_NET;
   uint_16                 PORT;
} ;

static void NAT_delete_func
   (
      pointer _PTR_  node_ptr_ptr,
      pointer        data_ptr
   )
{ /* Body */
   NAT_SESSION_STRUCT_PTR _PTR_ search_ptr_ptr = (NAT_SESSION_STRUCT_PTR _PTR_)node_ptr_ptr;
   struct nat_delete _PTR_      testdata = data_ptr;

  
   /* Search through list */
   while (*search_ptr_ptr) {
      if (testdata->TO_PRV_NET) {   /* Searching the IN tree */
         if ((*search_ptr_ptr)->PUB_HST == testdata->SRC      &&
             (*search_ptr_ptr)->NAT_HST == testdata->DEST     &&
             (*search_ptr_ptr)->PUB_PORT == testdata->PORT)
         {
            *search_ptr_ptr = (*search_ptr_ptr)->NEXT_IN;
            break;
         } /* Endif */
         search_ptr_ptr = &(*search_ptr_ptr)->NEXT_IN;
      } else {
         if ((*search_ptr_ptr)->PRV_HST == testdata->SRC      &&
            (*search_ptr_ptr)->PUB_HST == testdata->DEST     &&
            (*search_ptr_ptr)->PUB_PORT == testdata->PORT)
         {
            *search_ptr_ptr = (*search_ptr_ptr)->NEXT_OUT;
            break;
         } /* Endif */
         search_ptr_ptr = &(*search_ptr_ptr)->NEXT_OUT;
      } /* Endif */
   } /* Endwhile */

} /* Endbody */

void NAT_delete
   (
      NAT_SESSION_STRUCT_PTR   nat_session_ptr       /* [IN] Session to delete */
   )
{ /* Body */
   NAT_CFG_STRUCT_PTR         nat_cfg_ptr = RTCS_getcfg(NAT);
   NAT_SESSION_EXT_STRUCT_PTR nat_session_ext_ptr, next_nat_session_ext_ptr;
   struct nat_delete          data;
   uint_32                    key;

   /* Remove session from IN tree */
   key = NAT_KEY(nat_session_ptr->IP_PROT,nat_session_ptr->NAT_PORT);
   
   data.SRC = nat_session_ptr->PUB_HST;
   data.DEST = nat_session_ptr->NAT_HST;
   data.PORT = nat_session_ptr->PUB_PORT;
   data.TO_PRV_NET = TRUE;

   IPRADIX_insert(&nat_cfg_ptr->ROOT_IN, key, 0xFFFFFFFF, 0, NAT_delete_func,
      &data);

   /* Remove session from OUT tree */
   key = NAT_KEY(nat_session_ptr->IP_PROT, nat_session_ptr->PRV_PORT);

   data.SRC = nat_session_ptr->PRV_HST;
   data.DEST = nat_session_ptr->PUB_HST;
   data.PORT = nat_session_ptr->PUB_PORT;
   data.TO_PRV_NET = FALSE;

   IPRADIX_insert(&nat_cfg_ptr->ROOT_OUT, key, 0xFFFFFFFF, 0, NAT_delete_func,
      &data);

   nat_session_ext_ptr = nat_session_ptr->ALG_INFO_PTR;
   while (nat_session_ext_ptr)  {
      next_nat_session_ext_ptr = nat_session_ext_ptr->NEXT;
      if (NAT_alg_free_func_table[nat_session_ext_ptr->ALG_TYPE])  {
         NAT_alg_free_func_table[nat_session_ext_ptr->ALG_TYPE](nat_session_ext_ptr);
      } /* Endif */
      nat_session_ext_ptr = next_nat_session_ext_ptr;
   } /* Endwhile */

   nat_cfg_ptr->STATS.ST_SESSIONS_OPEN--;
   RTCS_part_free(nat_session_ptr);
} /* Endbody */


/*FUNCTION*-------------------------------------------------------------
*
* Function Name   : NAT_find_next_session
* Returned Value  : void
* Comments        :
*        FInds the next NAT session.
*
*END*-----------------------------------------------------------------*/

struct nat_find_next_session {
   NAT_SESSION_STRUCT_PTR  LAST_SESSION_PTR;
   NAT_SESSION_STRUCT_PTR  NEXT_SESSION_PTR;
   boolean                 FOUND_LAST;
   boolean                 FOUND_NEXT;
   uint_32                 TREE;
};

boolean NAT_find_next_session_test(  
   uint_32  key,
   uint_32  mask,
   pointer  node_pointer,
   pointer  data_pointer
)
{ 
   NAT_SESSION_STRUCT_PTR  nat_session_ptr = node_pointer;
   struct nat_find_next_session _PTR_ data_ptr = data_pointer;

   if (node_pointer== NULL) {
      return FALSE;
   }

   while (nat_session_ptr != NULL) {
      if (data_ptr->FOUND_LAST || (data_ptr->LAST_SESSION_PTR ==NULL )) {
         data_ptr->NEXT_SESSION_PTR = nat_session_ptr;
         data_ptr->FOUND_NEXT = TRUE;
         return TRUE;
      } else {
         if (data_ptr->LAST_SESSION_PTR == nat_session_ptr) {
            data_ptr->FOUND_LAST = TRUE;
         }
      }
      if (data_ptr->TREE) {
         nat_session_ptr = nat_session_ptr->NEXT_IN;
      } else {
         nat_session_ptr = nat_session_ptr->NEXT_OUT;
      }
   }
   return FALSE;
}

void NAT_find_next_session_internal
   (
      NAT_PARM_PTR           parm_ptr 
   )
{ /* Body */
   NAT_CFG_STRUCT_PTR   nat_cfg_ptr = RTCS_getcfg(NAT);
    struct nat_find_next_session    data;
   IPRADIX_NODE_PTR     root_ptr;


   data.LAST_SESSION_PTR = parm_ptr->CONFIG;
   data.NEXT_SESSION_PTR = NULL;
   data.FOUND_LAST = FALSE;
   data.FOUND_NEXT = FALSE;
   data.TREE = parm_ptr->OPTION;

   /* Find out if the packet is entering or exiting the private network */
   if (data.TREE) {
      root_ptr = &nat_cfg_ptr->ROOT_IN;
   } else {
      root_ptr = &nat_cfg_ptr->ROOT_OUT;
   } /* Endif */


   IPRADIX_walk(root_ptr, NAT_find_next_session_test, &data);
   if (data.FOUND_NEXT) {
      parm_ptr->CONFIG = data.NEXT_SESSION_PTR;
   } else {
      parm_ptr->CONFIG = NULL;
   }

   RTCSCMD_complete(parm_ptr, RTCS_OK);
} /* Endbody */

#endif
