www.pudn.com > Firewall_PNE_3_3.zip > fwStateLib.c, change:2009-03-16,size:36975b


/* fwStateLib.c - firewall stateful inspection module */

/* Copyright 2004-2005 Wind River Systems, Inc. */
#include "copyright_wrs.h"

/*
modification history
--------------------
01m,16aug05,zhu  added stateful connection max limit
01l,28apr05,zhu  fixed byte order in show routine
01k,26apr05,zhu  updated for gtfTimerManagerC
01j,09mar05,svk  Fix compilation warning
01i,13dec04,myz  added IPV6 support
01h,13jul04,myz  move common header files to fw.h
01g,25may04,svk  Clarify state entry timeout unit is seconds
01f,04mar04,svk  set default timeouts for ICMP/UDP/TCP state entries
01e,13feb04,myz  allocated own memory for pEvtFuncTbl
01d,11feb04,myz  call END event func if there is one inside the timeout function
01c,30jan04,myz  public API name changes
01b,29jan04,myz  change element state to connTrack  
01a,15oct03,myz  written
*/

/* 
DESCRIPTION

This module provides stateful packet inspection. This feature enables  
the firewall to identify whether the current packet belongs to a previously
established connection or is a response to the previous request. The stateful
inspection works in conjunction with the rule filter. In fact, its usage is 
controlled through the state rule field of the rule entry. The rule 
filter invokes this inspection engine when the state field of the given rule
entry is specified. The state field specifies two set of match requirements: 
which end(initiator or responder) of the connection the packet comes from and 
what state the connection is in. If it is the first legitimate packet from the
initiator, the inspection engine creates a state entry which records the IP 
address and port information for this connection. Therefore subsequent packets 
from both initiator and responder can be checked for whether the connection
state matches ones specified in the state rule field. The created state entries
will be removed at the end of the connection. For connectionless UDP protocol,
the entries are removed by a timer function when timeout occurs. For ICMP,
this module matches the ICMP echo replies with echo requests. The entries are 
removed when echo replies are received. For TCP, this module tracks the states
of the TCP connection. The state entries are removed at the end of connection.
All entries are associated with a timeout value. Currently the default timeout
is 2 minutes.  

INTERNAL:
Accessing state table data structure from the Firewall installation points in
the IP stack should be protected by the pair splnet()/splx() for mutual
exclusion.

*/

#include "fw.h"

#undef FW_STATELIB_DEBUG

#ifdef FW_STATELIB_DEBUG

int fwStateDebug = 0;
#define DBG_PRINT(X) \
    do { \
    if (fwStateDebug) \
        printf X; \
    }while(0)
#else

#define DBG_PRINT(X)

#endif

#define tcpState  tu.tcp.u.state
#define tcpFlag   tu.tcp.u.flag

#define SLIST_SIZE_MAX 64  /* must be 2**x, x = 1,2,3, .... default 8 */
#define HASH_CODE_LEN_MASK  (SLIST_SIZE_MAX - 1)
#define MEM_RESOURCE_SIZE(pMem) \
	((pMem)->numOfEntries * sizeof(PKT_STATE_ENTRY))

/* these values are used as part of index calculation. Do not change */

#define PACKET_FROM_CLIENT   1
#define PACKET_FROM_SERVER   0

#define CONNECTION_INVALID     0
#define CONNECTION_NEW         0x1
#define CONNECTION_ESTABLISHED 0x2
#define CONNECTION_END       0x10
#define TCP_CFLAG_MASK       0x17
#define TCP_SYNACK_BITS      (TH_SYN | TH_ACK)

/* These event values are directly used as index for the array type
 * TCP_NEXT_STATE. Do not change the values already defined. 
 */
#define TCPS_SYNACK_EVENT    0     /* SYN and ACK bit both set, from server */
#define TCPS_ACK_EVENT       1     /* only ACK bit set, from server */
#define TCPC_ACK_EVENT       2     /* only ACK bit set, from client */
#define TCPS_FINISH_EVENT    3     /* FIN bit set, from server */
#define TCPC_FINISH_EVENT    4     /* FIN bit set, from client */
#define TCPS_RESET_EVENT     5     /* RESET bit set */
#define TCPC_RESET_EVENT     6     /* RESET bit set */
#define TCP_LAST_EVENT       TCPC_RESET_EVENT
#define TCP_OTHER_EVENT    (TCP_LAST_EVENT + 1)

#define ONE_SECOND      1

/* max number of entries in stateful table for internal use */ 
#define FW_STATE_CONN_MAX 1000

typedef struct {
    int useFlag;
    PKT_STATE_ENTRY entry;
    } PKT_STATE_ENTRY_CACHE;

/* Memory resource node, used to keep track allocated memory */

typedef struct
    {
    DL_NODE node;
    void *  pEntryStart;
    int     numOfEntries;
    LIST    freeEntryList;
    } MEM_RESOURCE;

typedef struct {
     UINT8 event;
     UINT8 state;
     } TCP_NEXT_STATE;

typedef struct {
    TCP_NEXT_STATE next[TCP_OTHER_EVENT + 1];
    } TCP_STATE_ENTRY;

typedef struct {
    int icmpTimeout;
    int udpTimeout;
    int tcpTimeout;
    } STATE_CFG_PARAMS;

/* Local variables */
LOCAL int fwStateConnCount = 0;

LOCAL STATE_CFG_PARAMS cfgParams = {0,0,0};

LOCAL BOOL fwStateInitDone = FALSE;

/* this is the cache PKT_STATE_ENTRY tables, the index is derived from a
 * hash code. If matched entry has a collision, it is put on the list
 * associated with the cache entry. 
 */

LOCAL PKT_STATE_ENTRY_CACHE sEntryList[SLIST_SIZE_MAX];

/* this keeps track of the allocated memory chunk for the free PKT_STATE_ENTRY
 * entries. In initialization time, a pre-defined quantities of the free
 * entries are allocated and put on this list. This is to replace the use of
 * the costly malloc/free when a size of PKT_STATE_ENTRY memory is needed.
 */
LOCAL DL_LIST memList;


/* This TCP client end state table does not capture all the states described
 * by TCP protocol. It simply try to establish the start, establish and end
 * states enough to serve the TCP stateful inspection. 
 */

LOCAL GTF_TIMER * stateTimerId = NULL;

LOCAL TCP_STATE_ENTRY tcpClientStateTbl[] = 
    { 
    /*TCP_SYN_SENT */
        { 
	   {{TCPS_SYNACK_EVENT, TCP_ESTABLISH_STATE},
            {TCPS_ACK_EVENT,TCP_SYN_SENT}, 
            {TCPC_ACK_EVENT,TCP_SYN_SENT}, 
            {TCPS_FINISH_EVENT,TCP_CLOSEWAIT_STATE},
            {TCPC_FINISH_EVENT,TCP_FINWAIT_STATE},
            {TCPS_RESET_EVENT, TCP_END_STATE},
            {TCPC_RESET_EVENT, TCP_END_STATE},
            {TCP_OTHER_EVENT, TCP_SYN_SENT}}  /* stay same state */
        },

        /*TCP_ESTABLISH_STATE */
        {
           {{TCPS_SYNACK_EVENT, TCP_ESTABLISH_STATE}, 
            {TCPS_ACK_EVENT,TCP_ESTABLISH_STATE}, /*  stay same state */
            {TCPC_ACK_EVENT,TCP_ESTABLISH_STATE},     /* stay same state  */
            {TCPS_FINISH_EVENT,TCP_CLOSEWAIT_STATE},
            {TCPC_FINISH_EVENT,TCP_FINWAIT_STATE},
            {TCPS_RESET_EVENT, TCP_END_STATE},
            {TCPC_RESET_EVENT, TCP_END_STATE},
            {TCP_OTHER_EVENT, TCP_ESTABLISH_STATE}}  /* stay same state */
        },

        /*TCP_CLOSEWAIT_STATE */
        {
           {{TCPS_SYNACK_EVENT, TCP_CLOSEWAIT_STATE},  /* not a valid event */
            {TCPS_ACK_EVENT,TCP_END_STATE}, 
            {TCPC_ACK_EVENT,TCP_CLOSEWAIT_STATE},     /* stay same state  */
            {TCPS_FINISH_EVENT,TCP_CLOSEWAIT_STATE},
            {TCPC_FINISH_EVENT,TCP_CLOSEWAIT_STATE},
            {TCPS_RESET_EVENT, TCP_END_STATE},
            {TCPC_RESET_EVENT, TCP_END_STATE},
            {TCP_OTHER_EVENT, TCP_CLOSEWAIT_STATE}}  /* stay same state */
        },

        /*TCP_FINWAIT_STATE */
        {
           {{TCPS_SYNACK_EVENT, TCP_CLOSEWAIT_STATE},  /* not a valid event */
            {TCPS_ACK_EVENT,TCP_FINWAIT_STATE}, 
            {TCPC_ACK_EVENT,TCP_END_STATE},     /* stay same state  */
            {TCPS_FINISH_EVENT,TCP_FINWAIT_STATE},
            {TCPC_FINISH_EVENT,TCP_FINWAIT_STATE},
            {TCPS_RESET_EVENT, TCP_END_STATE},
            {TCPC_RESET_EVENT, TCP_END_STATE},
            {TCP_OTHER_EVENT, TCP_FINWAIT_STATE}}  /* stay same state */
        }
        /* TCP_END_STATE */
    };

/* externals */

IMPORT GTF_TIMER_MANAGER *  gtfTimerManagerCB;             /* timer manager                */

LOCAL int freeEntriesAllocate (int);
LOCAL PKT_STATE_ENTRY * freeEntryGet (void);
LOCAL void freeEntryPutback (NODE *);
LOCAL PKT_STATE_ENTRY * stateMatchEntryGet (int,PKT_STATE_ENTRY_CACHE *,
                              PKT_ADRS_INFO *, PKT_HDRS_INFO *);
LOCAL void stateInfoRecordCreate (PKT_STATE_ENTRY_CACHE *,
                              PKT_ADRS_INFO *, PKT_HDRS_INFO *,
			      FW_RULE_ENTRY_NODE *);
LOCAL int stateInfoUpdate (int, PKT_ADRS_INFO *, PKT_HDRS_INFO *,
                              PKT_STATE_ENTRY *);

LOCAL void timeoutEntryDelete (void);
LOCAL STATUS timeoutFuncRegister (void);


/******************************************************************************
*
* fwStateInit - Initialize the stateful inspection engine
*
* This routine allocates and initializes the required resource for running
* the stateful inspection engine. This function must be called first before
* any stateful inspection is done.
* 
* RETURNS: N/A
*/

void fwStateInit (void)
    {
    if (fwStateInitDone == FALSE)
	{
        dllInit(&memList);
        bzero((char *)sEntryList,sizeof(PKT_STATE_ENTRY) * SLIST_SIZE_MAX);
        freeEntriesAllocate(SLIST_SIZE_MAX);
       
	/* Set default timeouts for ICMP/UDP/TCP state entries */

	fwStateEntryTimeoutsSet(FW_CONN_ICMP_TIMEOUT, FW_CONN_UDP_TIMEOUT,
				FW_CONN_TCP_TIMEOUT);

	/* Start a timer */
        
	timeoutFuncRegister();

	fwStateInitDone = TRUE;
	}
    }

/******************************************************************************
*
* fwStateCleanup - Clean up the allocated resources 
*
* This function frees the allocated resource for the stateful inspection 
* engine. It should only be called after the rule filter has been uninstalled.
*
* RETURNS: N/A
*/

void fwStateCleanup (void)
    {
    if (fwStateInitDone == TRUE)
	{
	MEM_RESOURCE * pMemNode;
	MEM_RESOURCE * pMemNodeNext;

	pMemNode = (MEM_RESOURCE *)DLL_FIRST(&memList);

	while (pMemNode)
	    {
	    pMemNodeNext = (MEM_RESOURCE *)DLL_NEXT(&pMemNode->node);
            free(pMemNode);
	    pMemNode = pMemNodeNext;
	    }

        /* Cancel any pending GTF Timer */

        gtfTimerDelete (gtfTimerManagerCB, stateTimerId);
        free(stateTimerId);
        
        fwStateInitDone = FALSE;
        }
    }

/******************************************************************************
*
* freeEntriesAllocate - Allocate a given number of free entries.
*
*/

LOCAL int freeEntriesAllocate
    (
    int numEntries
    )
    {
    MEM_RESOURCE * pMem;
    PKT_STATE_ENTRY * pEntry; 
    int len;
    int i;

    len = sizeof(MEM_RESOURCE) + sizeof(PKT_STATE_ENTRY) * numEntries;

    if ((pMem = (MEM_RESOURCE *)malloc(len)) == NULL)
        {
        printf("ERROR: fwStateInit: malloc fails\n");
        return ERROR;
        }

    bzero((char *)pMem,len);
    lstInit(&pMem->freeEntryList);
    pMem->pEntryStart = (void *)(pMem+1);

    pEntry = (PKT_STATE_ENTRY *)pMem->pEntryStart;

    /* carve up the memory to the list entries */

    for (i = 0; i < numEntries; i++)
        lstAdd(&pMem->freeEntryList, &((pEntry + i)->u.lnode));

    pMem->numOfEntries = numEntries; 
    dllAdd(&memList,&pMem->node);   
    return OK; 
    }        

/******************************************************************************
*
* freeEntryGet - Get a entry from a free entry pool
*
*/

LOCAL PKT_STATE_ENTRY * freeEntryGet (void)
    {
    MEM_RESOURCE * pMem;
    PKT_STATE_ENTRY * pEntry = NULL;
    int i;

    for (i = 0; i < 2; i++)
        {
        pMem = (MEM_RESOURCE *)DLL_FIRST(&memList);

        while (pMem)
            {
            if (pMem->freeEntryList.count)
                {
                pEntry = (PKT_STATE_ENTRY *)lstGet(&pMem->freeEntryList);

                if (pEntry == NULL)
                    printf("ERROR: freeEntryGet,program error,list fails\n");

                return pEntry;
                }
            pMem = (MEM_RESOURCE *)DLL_NEXT((NODE *)pMem);
            } 

        /* If reaches here, the free list has exhausted, allocate more */

        freeEntriesAllocate(SLIST_SIZE_MAX);
        }

    /* should never reach here, if so, we have a program error */

    printf("ERROR: freeEntryGet, program error, list allocation fails\n");
    return NULL;
    }

/******************************************************************************
*
* freeEntryPutback - Put back the entry back to the free entry pool
*
*/

LOCAL void freeEntryPutback 
    (
    NODE * pNode
    )
    {
    MEM_RESOURCE * pMem;
    MEM_RESOURCE * pMemNext;

    pMem = (MEM_RESOURCE *)DLL_FIRST(&memList);

    while (pMem)
        {
        /* check if this free entry belongs to this memory block */

        if (((void *)pNode >= pMem->pEntryStart) && ((void *)pNode < 
            (void *)((UINT32)pMem->pEntryStart + MEM_RESOURCE_SIZE(pMem))) )
            {
            /* yes, it is this memory block */

            lstAdd(&pMem->freeEntryList,pNode);
            break;
            }
        pMem = (MEM_RESOURCE *)DLL_NEXT((DL_NODE *)pMem);
        }

    /* see if there is a redundent unused memory block, if so free it */

    pMem = (MEM_RESOURCE *)DLL_FIRST(&memList);

    /* always keep the first one */

    if (pMem)
        pMem =(MEM_RESOURCE *)DLL_NEXT((DL_NODE *)pMem);
 
    while (pMem)
        {
        pMemNext = (MEM_RESOURCE *)DLL_NEXT((DL_NODE *)pMem);

        if (pMem->freeEntryList.count == pMem->numOfEntries)
            {
            dllRemove(&memList,(DL_NODE *)pMem);
            free(pMem);
            }
        pMem = pMemNext;
        }
    }

/*****************************************************************************
*
* stateMatchEntryGet - Get a state match entry
*
* RETURNS: the matched state entry or NULL no match entry found
*/

LOCAL PKT_STATE_ENTRY * stateMatchEntryGet
    (
    int dir,
    PKT_STATE_ENTRY_CACHE * pCacheEntry,
    PKT_ADRS_INFO * pIpAdrs,
    PKT_HDRS_INFO * pPktHdrs
    )

    {
    PKT_STATE_ENTRY * pEntry;
    BOOL condition;
    TRANSPORT_STATE_INFO * pTranHeader = pPktHdrs->pTport;

    /* first check if exists */

    pEntry = &pCacheEntry->entry;

    if (pCacheEntry->useFlag == FALSE && (!DLL_FIRST(&pEntry->u.list)) )
	return NULL;  /* no entry found for this hash code */

    if (pCacheEntry->useFlag != TRUE)
        {
        /* cache entry not used, skip it */

        pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);
        }

    if (dir == PACKET_FROM_CLIENT)
        {
        while (pEntry)
            {
#ifdef INET6
            if (pPktHdrs->ipVer == IPV6_VERSION)
                condition = b4cmp(pEntry->SRCADDRV632,
                                  pIpAdrs->SRCADDRV632,4) == 0 &&
                            b4cmp(pEntry->DSTADDRV632,
                                  pIpAdrs->DSTADDRV632,4) == 0 &&
                            pEntry->proto          == pPktHdrs->proto;
            else
#endif
                condition = pEntry->SRCADDRV4 == pIpAdrs->SRCADDRV4 &&
                            pEntry->DSTADDRV4 == pIpAdrs->DSTADDRV4 &&
                            pEntry->proto          == pPktHdrs->proto;

            if (condition)
                {
                if (pEntry->tu.data[0] == pTranHeader->data[0])
                    break;
                }
            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);
            }
        }
    else /* PACKET from server */
        {
        union {
            UINT32 data;
            UINT16 data16[2];
            } tport;

        /* swap the source and destination ports */
 
        if (pPktHdrs->proto == IPPROTO_TCP || pPktHdrs->proto == IPPROTO_UDP)
            {
            tport.data16[0] = pTranHeader->data16[1];
            tport.data16[1] = pTranHeader->data16[0];
            }
        else  /* ICMP */
            {
            tport.data = pTranHeader->data[0];
            }

        while (pEntry)
            {
#ifdef INET6
            if (pPktHdrs->ipVer == IPV6_VERSION)
                condition = b4cmp(pEntry->SRCADDRV632,
                                  pIpAdrs->DSTADDRV632,4) == 0 &&
                            b4cmp(pEntry->DSTADDRV632,
                                  pIpAdrs->SRCADDRV632,4) == 0 &&
                            pEntry->proto          == pPktHdrs->proto;
            else
#endif
                condition = pEntry->SRCADDRV4 == pIpAdrs->DSTADDRV4 &&
                            pEntry->DSTADDRV4 == pIpAdrs->SRCADDRV4 &&
                            pEntry->proto          == pPktHdrs->proto;

            if (condition)
                {
                if (pEntry->tu.data[0] == tport.data)
                    break;
                }
            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);
            }
        }
    return pEntry;
    }

/******************************************************************************
*
* connectionStateCheck - Perform the state action for the inspected packet
*
* This routine acts as the stateful inspection engine. It creates the state
* entry on the first legitimate packet of the connection; performs state
* transition; obtains the connection state and finally returns the match 
* condition for the state rule field. 
*
* RETURNS: TRUE if a match found, FALSE not a match
* NOMANUAL
*
*/

int connectionStateCheck
    (
    FW_RULE_ENTRY_NODE * pMatch,
    PKT_ADRS_INFO * pIpAdrs,
    PKT_HDRS_INFO * pPktHdrs
    )
    {
    PKT_STATE_ENTRY_CACHE * pCacheEntry;
    PKT_STATE_ENTRY * pEntry;
    TRANSPORT_STATE_INFO * pTranHeader = pPktHdrs->pTport;
    int hashCode;
    BOOL matchCode = FALSE;

    DBG_PRINT(("fwState: enter state check, Rule state type: 0x%x\n",
	       pMatch->rule.connTrack.state));

#ifdef INET6
    if (pPktHdrs->ipVer == IPV6_VERSION)
        hashCode = pIpAdrs->SRCADDRV632[3] + pIpAdrs->DSTADDRV632[3];
    else
#endif
        hashCode = pIpAdrs->SRCADDRV4 + pIpAdrs->DSTADDRV4;
        
    hashCode += pTranHeader->data16[0] + pTranHeader->data16[1];
    hashCode &= HASH_CODE_LEN_MASK;

    pCacheEntry = &sEntryList[hashCode];

    pEntry = stateMatchEntryGet(pMatch->rule.connTrack.entity == 
				FW_CONN_INITIATOR? 
                     PACKET_FROM_CLIENT:PACKET_FROM_SERVER,
                     pCacheEntry,pIpAdrs,pPktHdrs);

    if (pEntry == NULL)
        {
        if (pMatch->rule.connTrack.entity == FW_CONN_INITIATOR)
	    {
	    /* First time, see if to create a tracking entry */

	    if (pMatch->rule.connTrack.state & FW_CONN_STATE_CREATE)
                {
                if (fwStateConnCount < FW_STATE_CONN_MAX)
                    {
                    stateInfoRecordCreate(pCacheEntry, pIpAdrs, pPktHdrs, 
                                          pMatch);
                    fwStateConnCount++;
                    }
               /* if fwStateConnCount exceeds the max, still return match
                * since the packet is an initial packet. But firewall will not
                * track the state.
                */                   
                matchCode = TRUE;
                }
            }
        else  /* FW_CONN_RESPONDER */
            {
            if (pMatch->rule.connTrack.state & FW_CONN_STATE_NEW)
	        matchCode = TRUE;   /* the connection has not been initiated */
            }
        }
    else
        {
        int connCode;

        DBG_PRINT(("fwState: packet from %s, entry found, type: %s\n",
                 pMatch->rule.connTrack.entity==FW_CONN_INITIATOR? 
                                     "client": "server",
                 pEntry == &pCacheEntry->entry? "cache": "normal"));

        /* update the connection state if necessary */

        connCode = stateInfoUpdate(
                           pMatch->rule.connTrack.entity == FW_CONN_INITIATOR? 
                            PACKET_FROM_CLIENT: PACKET_FROM_SERVER,
                            pIpAdrs,pPktHdrs,pEntry);
        if (connCode == CONNECTION_END)
            {
            if (pEntry->pEvtFuncTbl)
                free(pEntry->pEvtFuncTbl);
 
            /* remove this entry */

            if (pEntry == &pCacheEntry->entry)
                {
                /* cache entry, simply mark it as free */

                pCacheEntry->useFlag = FALSE;
                DBG_PRINT(("fwState: cache entry freed\n"));
                }
            else
                {
                dllRemove(&pCacheEntry->entry.u.list,&pEntry->u.node);
                freeEntryPutback(&pEntry->u.lnode);
                DBG_PRINT(("fwState: entry removed from the list\n"));
                }

            fwStateConnCount--;
            }
        if (pMatch->rule.connTrack.state & FW_CONN_STATE_NEW)
            {
            if (connCode == CONNECTION_NEW)
                matchCode = TRUE;
            }
        if (pMatch->rule.connTrack.state & FW_CONN_STATE_ESTABLISHED)
            {
            if (connCode == CONNECTION_ESTABLISHED ||
                connCode == CONNECTION_END)
                matchCode = TRUE;
            } 
        }
    return matchCode;
    }

/******************************************************************************
*
* evtFuncsRetrieve - Get the event functions from the matched firewall rule
*
* This function retrieves the supplied event functions(For TCP connection) in
* the firewall rule and install it in the state entry. This is done only once
* when the state entry is created. The event functions will be invoked when
* a state transition occurs.
*
* RETURN: point to the event function table.
*/

LOCAL FW_EVENT_FUNC_ENTRY * evtFuncsRetrieve
    (
    FW_RULE_ENTRY_NODE * pNode,
    TRANSPORT_STATE_INFO * pTport,
    UINT8 ip_p 
    )
    {
    int i;

    if (!pNode->pExtDesc)
        return NULL;

    for (i = 0; i < pNode->eDescInx; i++)
        {
        /* don't care the service type */

        if (pNode->pExtDesc[i].service.proto == 0 &&
            pNode->pExtDesc[i].evtFuncs[0].pEventFunc != NULL)
            return (&pNode->pExtDesc[i].evtFuncs[0]); 
        else
            {
            /* match the service type */

            if (pNode->pExtDesc[i].service.proto == ip_p &&
                (pNode->pExtDesc[i].service.srcPort == 0 ||
                 pNode->pExtDesc[i].service.srcPort == pTport->data16[0]) &&
                (pNode->pExtDesc[i].service.dstPort == 0 ||
                 pNode->pExtDesc[i].service.dstPort == pTport->data16[1]) &&
                pNode->pExtDesc[i].evtFuncs[0].pEventFunc != NULL)
                {
                return (&pNode->pExtDesc[i].evtFuncs[0]);
                }
            }
        }
    return NULL;
    }


/******************************************************************************
*
* stateInfoRecordCreate - Create or update the state entry
*
* This function should be only called at the location specified as initiator
* It create a tracking state entry on the first packet sent by the initiator. 
*
* RETURNS: N/A
*/

LOCAL void stateInfoRecordCreate 
    (
    PKT_STATE_ENTRY_CACHE * pCacheEntry,
    PKT_ADRS_INFO * pIpAdrs,
    PKT_HDRS_INFO * pPktHdrs,
    FW_RULE_ENTRY_NODE * pMatch
    )
    {
    PKT_STATE_ENTRY * pEntry = NULL;
    BOOL condition = FALSE;
    int timeout = 0;
    TRANSPORT_STATE_INFO * pTport = pPktHdrs->pTport;

    DBG_PRINT(("FW State: entry created for %s\n",pPktHdrs->proto==IPPROTO_UDP?
               "UDP": pPktHdrs->proto == IPPROTO_TCP? "TCP": "ICMP"));

    switch (pPktHdrs->proto)
        {
        case IPPROTO_UDP:
            /* stateless, create an entry on first out packet */

            timeout = cfgParams.udpTimeout;
            condition = TRUE;
            break;

        case IPPROTO_TCP:
            /* The first packet should always has a TH_SYN only flag for the
	     * client.  For the TCP server,  the first packet sent out should
	     * be TH_SYN and TH_ACK upon receiving the TH_SYN from client.
             */

            timeout = cfgParams.tcpTimeout;
            if ((pTport->tcp.u.flag & TCP_CFLAG_MASK) == TH_SYN)
                condition = TRUE;
            break;
                    
        case IPPROTO_ICMP:

            /* keep track of the echo request initiated inside */

            timeout = cfgParams.icmpTimeout;
            if (pTport->icmp.type == ICMP_ECHO)
                condition = TRUE;
            break;

        case IPPROTO_ICMPV6:
            timeout = cfgParams.icmpTimeout;
            if (pTport->icmp.type == ICMP6_ECHO_REQUEST)
                condition = TRUE;
            break;
        default: 
	    break;
        }
   
    if (condition == FALSE)
        return; /* no entry created */

    if (pCacheEntry->useFlag == FALSE)
        {
        DBG_PRINT(("Cache entry used\n"));

        /* use cache entry */
       
        pEntry = &(pCacheEntry->entry);
	pEntry->u.list.head = NULL;
	pEntry->u.list.tail = NULL;
        pCacheEntry->useFlag = TRUE;
        }
    else
        {
        /* get one from free list */

        pEntry = freeEntryGet();
        dllAdd(&pCacheEntry->entry.u.list, (DL_NODE *)pEntry);
        }

    pEntry->ownerId = (void *)pMatch;

#ifdef INET6
    if (pPktHdrs->ipVer == IPV6_VERSION)
        {
        bcopyLongs((char *)pIpAdrs->SRCADDRV632,(char *)pEntry->SRCADDRV632,4);
        bcopyLongs((char *)pIpAdrs->DSTADDRV632,(char *)pEntry->DSTADDRV632,4);
        }
    else
#endif
        {
        pEntry->SRCADDRV4 = pIpAdrs->SRCADDRV4;
        pEntry->DSTADDRV4 = pIpAdrs->DSTADDRV4;
        }

    pEntry->proto     = pPktHdrs->proto;
    pEntry->ipVer     = pPktHdrs->ipVer;

    pEntry->tu = *pTport;

    if (timeout)    
        pEntry->timeout = timeout;
    else
        {
        if (pPktHdrs->proto == IPPROTO_TCP)
            pEntry->timeout = FW_CONN_TCP_TIMEOUT;
        else if (pPktHdrs->proto == IPPROTO_UDP)
            pEntry->timeout = FW_CONN_UDP_TIMEOUT;
        else
            pEntry->timeout = FW_CONN_ICMP_TIMEOUT;
        }

    DBG_PRINT(("timeout value: %d\n",pEntry->timeout));

    if (pPktHdrs->proto == IPPROTO_UDP)
        pEntry->tu.udp.state = CONNECTION_NEW;

    if (pPktHdrs->proto == IPPROTO_TCP)
	{
        FW_EVENT_FUNC_ENTRY * pEvtTbl;

        pEntry->tcpState = TCP_SYN_SENT;

	/* check if there is a matched event function table to be installed */
 
        pEvtTbl = evtFuncsRetrieve(pMatch,pTport,pPktHdrs->proto);
        if (pEvtTbl)
            {
            pEntry->pEvtFuncTbl = malloc(sizeof(FW_EVENT_FUNC_ENTRY) *
                                         FW_EVT_FUNC_NUM);
            if (pEntry->pEvtFuncTbl)
                bcopy((char *)pEvtTbl, (char *)(pEntry->pEvtFuncTbl),
                      sizeof(FW_EVENT_FUNC_ENTRY) * FW_EVT_FUNC_NUM);
            } 
        else
            pEntry->pEvtFuncTbl = NULL;

        /* we're now in the SYNC_SENT state, invoke event functions if any */

        if (pEntry->pEvtFuncTbl)
            {
            int i;

            for (i = 0; i < FW_EVT_FUNC_NUM; i++)
                {
                if (pEntry->pEvtFuncTbl[i].pEventFunc)
                    {
                    if (pEntry->pEvtFuncTbl[i].state == pEntry->tcpState)
                        (*pEntry->pEvtFuncTbl[i].pEventFunc)(
                                 pEntry->pEvtFuncTbl[i].eventFuncArg);
                    }
                }
            }
	}
    }

/******************************************************************************
*
* stateInfoUpdate - Update a connection tracking state entry
*
* RETURNS: connection codes
*/

LOCAL int stateInfoUpdate
    (
    int dir,
    PKT_ADRS_INFO * pIpAdrs,
    PKT_HDRS_INFO * pPktHdrs,
    PKT_STATE_ENTRY * pEntry
    )
    {
    UINT8 event;
    int i;
    int retCode = CONNECTION_INVALID;
    TRANSPORT_STATE_INFO * pTranHeader = pPktHdrs->pTport;

    switch (pPktHdrs->proto)
        {
        case IPPROTO_UDP:
            /* stateless, reset the timeout value */
        
	    pEntry->timeout = cfgParams.udpTimeout? 
                              cfgParams.udpTimeout:
                              FW_CONN_UDP_TIMEOUT;
            if (dir == PACKET_FROM_SERVER)
                pEntry->tu.udp.state = CONNECTION_ESTABLISHED;
           
            retCode = pEntry->tu.udp.state;
            return retCode;
 
            break;

        case IPPROTO_TCP:
            {
	    UINT8 curState;

	    /* convert flag bits combination into a single event */

	    if ((pTranHeader->tcp.u.flag & TCP_CFLAG_MASK) == TH_ACK)
		event = TCPS_ACK_EVENT +dir;
	    else if ((pTranHeader->tcp.u.flag & TCP_SYNACK_BITS)==
		TCP_SYNACK_BITS && dir == PACKET_FROM_SERVER)
		event = TCPS_SYNACK_EVENT;
            else if (pTranHeader->tcp.u.flag & TH_RST)
                event = TCPS_RESET_EVENT + dir;
            else if (pTranHeader->tcp.u.flag & TH_FIN)
                event = TCPS_FINISH_EVENT + dir;
            else
                event = TCP_OTHER_EVENT;

            /* derive the next state from the current state and received event*/

	    curState = pEntry->tcpState;

	    if (curState == TCP_END_STATE)  /* should not happen */
                return retCode;

            pEntry->tcpState = 
                      tcpClientStateTbl[curState].next[event].state;

	    /* update the timeout */

	    pEntry->timeout = cfgParams.tcpTimeout?
                              cfgParams.tcpTimeout:
                              FW_CONN_TCP_TIMEOUT;

	    /* if there is a state change, invoke event functions */

            if (curState != pEntry->tcpState)
                {
                if (pEntry->pEvtFuncTbl)
                    {
                    for (i = 0; i < FW_EVT_FUNC_NUM; i++)
                        {
                        if (pEntry->pEvtFuncTbl[i].pEventFunc)
                            {
                            if (pEntry->pEvtFuncTbl[i].state==pEntry->tcpState)
                                (*pEntry->pEvtFuncTbl[i].pEventFunc)(
                                         pEntry->pEvtFuncTbl[i].eventFuncArg);
                            }
                        }
                    }
                }

            /* return the proper state code */

            if (pEntry->tcpState == TCP_SYN_SENT)
                retCode = CONNECTION_NEW;
            else if ((pEntry->tcpState >= TCP_ESTABLISH_STATE) &&
                     (pEntry->tcpState < TCP_END_STATE) )
                retCode = CONNECTION_ESTABLISHED;
            else if (pEntry->tcpState == TCP_END_STATE)
                retCode = CONNECTION_END;

            DBG_PRINT(("FWstateUpdate:TCPstate change to %d,TcpFlags:0x%x\n",
                      pEntry->tcpState, pTranHeader->tcp.u.flag));

            return retCode;
            }
            break;

        case IPPROTO_ICMP:
        case IPPROTO_ICMPV6:
            {
            int type;

            type = pPktHdrs->proto == IPPROTO_ICMP?
                   ICMP_ECHOREPLY:
                   ICMP6_ECHO_REPLY;
 
            /* keep track of the echo request initiated  */

            if (dir == PACKET_FROM_SERVER && 
                pTranHeader->icmp.type == type)
                retCode = CONNECTION_END;
            else 
		{
                retCode = CONNECTION_NEW;
                pEntry->timeout = cfgParams.icmpTimeout?
                                  cfgParams.icmpTimeout:
                                  FW_CONN_ICMP_TIMEOUT;
                DBG_PRINT(("timeout value: %d\n",pEntry->timeout));
		}
            }
            break;

        default:
	    break;
        }
    return retCode;
    }

/******************************************************************************
*
* timeoutEntryDelete - Delete the timeout entries
*
* This function should be called from a periodic timer at the interval of 
* 1 second by default. It checks the state entry table and deletes entries that
* are timed out. 
*
*/

LOCAL void timeoutEntryDelete (void)
    {
    PKT_STATE_ENTRY * pEntry;
    int i;
    int s;

    s = splnet();
    for (i = 0; i < SLIST_SIZE_MAX; i++)
        {
        pEntry = &sEntryList[i].entry; 

        if (sEntryList[i].useFlag != TRUE)
            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);

        while (pEntry)
            {

            if (pEntry->timeout > 0)
                pEntry->timeout--;   
 
            if (pEntry->timeout <= 0)
                {
                /* decrease the counters */
                fwStateConnCount--;
                /* remove this entry, first check the event functions */

                if (pEntry->pEvtFuncTbl)
                    {
                    /* execute the end state event function if there is one */

                    int i;

                    for (i = 0; i < FW_EVT_FUNC_NUM; i++)
                        {
                        if (pEntry->pEvtFuncTbl[i].state == TCP_END_STATE)
                            {
                            if (pEntry->pEvtFuncTbl[i].pEventFunc)
                                (*pEntry->pEvtFuncTbl[i].pEventFunc)(
                                         pEntry->pEvtFuncTbl[i].eventFuncArg);
                            }
                        }
                    /* free the memory */

                    free(pEntry->pEvtFuncTbl);
                    }

                if (pEntry == &sEntryList[i].entry)
                    {
                    /*cache entry,  simply mark it as free */

                    sEntryList[i].useFlag = FALSE;
                    }
                else
                    {
                    dllRemove(&sEntryList[i].entry.u.list,(DL_NODE *)pEntry);
                    freeEntryPutback((NODE *)pEntry);
                    }
                }
            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);
            } /* end while loop */
        }
    splx(s);

    /* fire up the timer again */

    gtfTimerReset(gtfTimerManagerCB,stateTimerId,ONE_SECOND*sysClkRateGet(),
                  (GTF_TIMER_HANDLER)timeoutEntryDelete, NULL);
    } 
            
/******************************************************************************
*
* timeoutFuncRegister - Register a timeout callback function with the NAT timer
*
*/

LOCAL STATUS timeoutFuncRegister (void)
    {
    
    /* start a 1 second timer */

    stateTimerId = malloc (sizeof (GTF_TIMER));

    if (stateTimerId == NULL)
        {
        printf("fail to create a timer for state module \n");
        return ERROR;
        }

    bzero((char *)stateTimerId,sizeof (GTF_TIMER));

    if (gtfTimerInit (gtfTimerManagerCB, (GTF_TIMER *)stateTimerId,
        ONE_SECOND * sysClkRateGet(), 
        (GTF_TIMER_HANDLER)timeoutEntryDelete, NULL) != 0)
        {
        free (stateTimerId);
        printf("fail to initialize the timer for state module\n");
        return ERROR;
        }

    gtfTimerStart (gtfTimerManagerCB, stateTimerId);
    return OK;
    }

/******************************************************************************
*
* fwStateEntriesShow - display the state entry table
*
* NOMANUAL
*/
void fwStateEntriesShow (void)
    {
    PKT_STATE_ENTRY * pEntry;
    int i;
    char srcStr[INET6_ADDRSTRLEN];
    char dstStr[INET6_ADDRSTRLEN];
    struct in_addr ipAddr;

    for (i = 0; i < SLIST_SIZE_MAX; i++)
        {
        pEntry = &sEntryList[i].entry;

        if (sEntryList[i].useFlag != TRUE)
            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);

        while (pEntry)
            {
#ifdef INET6
            if (pEntry->ipVer == IPV6_VERSION)
                {
                inet_ntop (AF_INET6, (void *)pEntry->SRCADDRV632,
                           srcStr,INET6_ADDRSTRLEN);
                inet_ntop (AF_INET6, (void *)pEntry->DSTADDRV632,
                           dstStr,INET6_ADDRSTRLEN);
                }
            else
#endif
                {
                ipAddr.s_addr = htonl(pEntry->SRCADDRV4);
                inet_ntoa_b(ipAddr,srcStr);
                ipAddr.s_addr = htonl(pEntry->DSTADDRV4);
                inet_ntoa_b(ipAddr,dstStr);
                }
 
            printf ("inx:%d, prot:%d, state:0x%x,ipSrc/Dst: %s/%s, %d/%d\n",
                    i, pEntry->proto, pEntry->tcpState, 
		    srcStr,
		    dstStr,pEntry->tu.data16[0],
                    pEntry->tu.data16[1]);

            pEntry = (PKT_STATE_ENTRY *)DLL_NEXT((DL_NODE *)pEntry);
            }
            
        }
    }

/*******************************************************************************
*
* fwStateEntryTimeoutsSet - set the state entry timeout values
*
* This routine sets the ICMP, UDP, and TCP state entry timeout values 
* in seconds.
*
* RETURNS: OK or ERROR
*
*/
STATUS fwStateEntryTimeoutsSet
    (
    int icmpTimeout,  /* ICMP type state entry timeout in seconds */
    int udpTimeout,   /* UDP type state entry timeout in seconds */
    int tcpTimeout    /* TCP type state entry timeout in seconds */
    )
    {

    if ((icmpTimeout < 0) || (udpTimeout < 0) || (tcpTimeout < 0))
        return ERROR;

    cfgParams.icmpTimeout = icmpTimeout;
    cfgParams.udpTimeout  = udpTimeout;
    cfgParams.tcpTimeout  = tcpTimeout;
    return OK;
    }