blob: 0ab6f860e5b743d5cf35717a28a1c4c2c9b8c7a0 [file] [log] [blame]
/*
* Copyright (c) 2009 Mindspeed Technologies, Inc.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
*/
#include "module_ipv4.h"
#include "module_ipv6.h"
#include "module_hidrv.h"
#include "module_timer.h"
#include "alt_conf.h"
#include "fpp.h"
#include "module_ipsec.h"
#include "module_rtp_relay.h"
#include "Module_ipv4frag.h"
#include "layer2.h"
#ifdef COMCERTO_1000
#include "module_msp.h"
#endif
#include "control_socket.h"
#include "fppdiag_lib.h"
#include "module_stat.h"
int IPv4_Get_Next_Hash_CTEntry(PCtExCommand pCtCmd, int reset_action);
int IPV4_Get_Next_Hash_RtEntry(PRtCommand pRtCmd, int reset_action);
extern TIMER_ENTRY ip_timer;
#if !defined(COMCERTO_2000)
U8 ct_proto[MAX_CONNTRACK_PROTO] = {IPPROTOCOL_TCP,IPPROTOCOL_UDP,IPPROTOCOL_IPIP};
#endif
#if defined(COMCERTO_2000)
struct dlist_head ct_removal_list;
/** Gets the flags field from an hardware conntrack.
* The function converts the value read from PFE/DDR format to host format (endianess conversion).
*
* @param ct pointer to an hardware conntrack
*
* @return flags field value
*
*/
U32 hw_ct_get_flags(struct hw_ct *ct)
{
return be32_to_cpu(readl(&ct->flags));
}
/** Sets the flags field in an hardware conntrack.
* The function converts the value written from host format to PFE/DDR format (endianess conversion).
*
* @param ct pointer to an hardware conntrack
* @param flags flags value to set
*
*/
void hw_ct_set_flags(struct hw_ct *ct, U32 flags)
{
/* FIXME if the entire conntrack is in non-cacheable/non-bufferable memory,
there should be no need for the memory barrier */
wmb();
writel(cpu_to_be32(flags), &ct->flags);
}
/** Gets the next field in an hardware conntrack.
* The function converts the value read from PFE/DDR format to host format.
*
* @param ct pointer to an hardware conntrack
*
* @return next field value (physical address)
*
*/
U32 hw_ct_get_next(struct hw_ct *ct)
{
return be32_to_cpu(readl(&ct->next));
}
/** Sets the next field in an hardware conntrack.
* The function converts the value written from host format to PFE/DDR format (endianess conversion).
*
* @param ct pointer to an hardware conntrack
* @param next next field value (physical address)
*
*/
void hw_ct_set_next(struct hw_ct *ct, U32 next)
{
/* FIXME if the entire conntrack is in non-cacheable/non-bufferable memory,
there should be no need for the memory barrier */
wmb();
writel(cpu_to_be32(next), &ct->next);
}
/** Gets the active field in an hardware conntrack.
* The function converts the value read from PFE/DDR format to host format.
*
* @param ct pointer to an hardware conntrack
*
* @return active field value
*
*/
U32 hw_ct_get_active(struct hw_ct *ct)
{
return be32_to_cpu(readl(&ct->active));
}
/** Sets the active field in an hardware conntrack.
* The function converts the value written from host format to PFE/DDR format (endianess conversion).
*
* @param ct pointer to an hardware conntrack
* @param active active field value
*
*/
void hw_ct_set_active(struct hw_ct *ct, U32 active)
{
writel(cpu_to_be32(active), &ct->active);
}
/** Allocates a new software conntrack.
* The originator and replier conntracks are allocated as a single object
*
* @return pointer to the new conntrack, NULL of error
*
*/
PCtEntry ct_alloc(void)
{
PCtEntry pEntry_orig;
PCtEntry pEntry_rep;
/* Allocate local entry */
pEntry_orig = pfe_kzalloc(2 * sizeof(CtEntry), GFP_KERNEL);
if (!pEntry_orig)
return NULL;
pEntry_rep = pEntry_orig + 1;
pEntry_orig->twin = pEntry_rep;
pEntry_rep->twin = pEntry_orig;
return pEntry_orig;
}
/** Frees a software conntrack.
* The function fress both originator and replier conntracks
*
* @param pEntry_orig pointer to the originator conntrack
*/
void ct_free(PCtEntry pEntry_orig)
{
/* Free local entry */
pfe_kfree(pEntry_orig);
}
struct dlist_head *ct_dlist_head(U32 hash)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
struct hw_ct *ct = ctrl->hash_array_baseaddr + hash * CLASS_ROUTE_SIZE;
return &ct->list;
}
static struct hw_ct *ct_container(struct dlist_head *entry)
{
return container_of(entry, typeof(struct hw_ct), list);
}
/** Adds a software conntrack to the hardware hash.
* The VALID flag is not set in this routine (must be set by the caller).
* In case of memory allocation error the conntrack is not added to the hash.
*
* @param pEntry pointer to the software conntrack
* @param hash hash index where to add the conntrack
*
* @return NO_ERR in case of success, ERR_xxx in case of error
*/
static int hw_ct_add_one(PCtEntry pEntry, U32 hash)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
struct hw_ct *ct, *prev = NULL;
int i;
/* FIXME add workaround for hardware fetch mismatch, if IPv4 add to head of list, otherwise to tail */
/* If using software fetch this can be avoided, otherwise it's a big mess since it may force us to take out
one existing entry from the hash array */
/* Allocate hardware entry */
/* If hash array bucket is empty, use it, otherwise allocate a new entry dynamically in the dma pool */
if (hw_ct_get_flags(ctrl->hash_array_baseaddr + hash * CLASS_ROUTE_SIZE) & CT_USED)
{
dma_addr_t dma_addr;
// printk(KERN_INFO "ct add collision\n");
ct = dma_pool_alloc(ctrl->dma_pool, GFP_ATOMIC, &dma_addr);
if (!ct)
{
printk(KERN_ERR "%s: dma_pool_alloc() failed\n", __func__);
goto err_alloc;
}
ct->dma_addr = cpu_to_be32(dma_addr);
/* Add new entry just after the hash array */
dlist_add(ct_dlist_head(hash), &ct->list);
prev = ct_container(dlist_prev(&ct->list));
hw_ct_set_next(ct, hw_ct_get_next(prev));
}
else
{
ct = ctrl->hash_array_baseaddr + hash * CLASS_ROUTE_SIZE;
}
// printk(KERN_INFO "%s %p\n", __func__, ct);
/* Update hardware entry */
ct->Sport = pEntry->Sport;
ct->Dport = pEntry->Dport;
ct->proto = pEntry->proto;
ct->hash = cpu_to_be16(pEntry->hash);
ct->twin_dma_addr = 0;
memcpy(ct->Saddr_v6, pEntry->Saddr_v6, 4 * 2 * sizeof(U32));
ct->fwmark = cpu_to_be16(pEntry->fwmark);
ct->status = cpu_to_be16(pEntry->status);
for (i = 0; i < SA_MAX_OP; i++)
{
ct->hSAEntry[i] = cpu_to_be16(pEntry->hSAEntry[i]);
}
hw_ct_set_active(ct, 0);
ct->ip_chksm_corr = pEntry->ip_chksm_corr;
ct->tcp_udp_chksm_corr = pEntry->tcp_udp_chksm_corr;
if(!(pEntry->status & CONNTRACK_IPv6))
{
ct->twin_Saddr = pEntry->twin_Saddr;
ct->twin_Daddr = pEntry->twin_Daddr;
ct->twin_Sport = pEntry->twin_Sport;
ct->twin_Dport = pEntry->twin_Dport;
}
if (IS_NULL_ROUTE(pEntry->pRtEntry))
{
/* Attach to route */
IP_Check_Route(pEntry);
}
if (!IS_NULL_ROUTE(pEntry->pRtEntry))
{
ct->route.itf = cpu_to_be32(virt_to_class(pEntry->pRtEntry->itf));
memcpy(ct->route.dstmac, pEntry->pRtEntry->dstmac, ETHER_ADDR_LEN);
ct->route.mtu = cpu_to_be16(pEntry->pRtEntry->mtu);
// ct->route.flags = pEntry->pRtEntry->flags;
ct->route.Daddr_v4= pEntry->pRtEntry->Daddr_v4;
}
else
{
ct->route.itf = 0xffffffff;
}
if (!IS_NULL_ROUTE(pEntry->tnl_route))
{
if(pEntry->status & CONNTRACK_4O6)
{
ct->tnl4o6_route.itf = cpu_to_be32(virt_to_class(pEntry->tnl_route->itf));
memcpy(ct->tnl4o6_route.dstmac, pEntry->tnl_route->dstmac, ETHER_ADDR_LEN);
ct->tnl4o6_route.mtu = cpu_to_be16(pEntry->tnl_route->mtu);
// ct->tnl_route.flags = pEntry->tnl_route->flags;
ct->tnl4o6_route.Daddr_v6[0] = pEntry->tnl_route->Daddr_v6[0];
ct->tnl4o6_route.Daddr_v6[1] = pEntry->tnl_route->Daddr_v6[1];
ct->tnl4o6_route.Daddr_v6[2] = pEntry->tnl_route->Daddr_v6[2];
ct->tnl4o6_route.Daddr_v6[3] = pEntry->tnl_route->Daddr_v6[3];
}
else
{
ct->tnl6o4_route.itf = cpu_to_be32(virt_to_class(pEntry->tnl_route->itf));
memcpy(ct->tnl6o4_route.dstmac, pEntry->tnl_route->dstmac, ETHER_ADDR_LEN);
ct->tnl6o4_route.mtu = cpu_to_be16(pEntry->tnl_route->mtu);
ct->tnl6o4_route.Daddr_v4 = pEntry->tnl_route->Daddr_v4;
}
}
else
{
if(pEntry->status & CONNTRACK_IPv6)
ct->tnl6o4_route.itf = 0xffffffff;
else
ct->tnl4o6_route.itf = 0xffffffff;
}
/* Link to hardware list */
if (!IS_HASH_ARRAY(ctrl, ct))
hw_ct_set_next(prev, be32_to_cpu(ct->dma_addr));
/* Link software conntrack to hardware conntrack */
pEntry->ct = ct;
ct->pCtEntry = pEntry;
/* Mark entry in use (valid flag will be set after twin entry is created) */
hw_ct_set_flags(ct, CT_USED);
return NO_ERR;
err_alloc:
IP_delete_CT_route(pEntry);
return ERR_NOT_ENOUGH_MEMORY;
}
/** Schedules an hardware conntrack for removal.
* The entry is added to a removal list and it will be free later from a timer.
* The removal time must be bigger than the worst case PE processing time for tens of packets.
*
* @param ct pointer to the hardware conntrack
*
*/
static void hw_ct_schedule_remove(struct hw_ct *ct)
{
ct->pCtEntry = NULL;
ct->removal_time = jiffies + 2;
dlist_add(&ct_removal_list, &ct->rlist);
}
/** Processes hardware conntrack delayed removal list.
* Free all hardware conntracks in the removal list that have reached their removal time.
* If the entry being free is in the hash array, then move the next entry (if any) to the hash array.
*
* @param ct pointer to the hardware conntrack
*
*/
static void hw_ct_delayed_remove(void)
{
struct pfe_ctrl *ctrl = &pfe->ctrl;
struct dlist_head *entry;
struct hw_ct *ct;
dlist_for_each_safe(ct, entry, &ct_removal_list, rlist)
{
// printk(KERN_INFO "%s %p\n", __func__, ct);
if (!time_after(jiffies, ct->removal_time))
continue;
dlist_remove(&ct->rlist);
if (IS_HASH_ARRAY(ctrl, ct))
{
hw_ct_set_flags(ct, 0);
/* If the hash bucket is not empty try to move one entry to the hash array */
/* This is a performance optimization, to avoid a hash collision even after
the entry in the hash array has been removed */
if (!dlist_empty(&ct->list))
{
struct hw_ct *next = ct_container(dlist_first(&ct->list));
// printk(KERN_INFO "moving next to hash array %p %p\n", next, &ct->list);
/* Copy contents except flags, next at the beginning and dma_addr, list, ... at the end */
memcpy((void *)&ct->Sport, (void *)&next->Sport, (void *)&ct->dma_addr - (void *)&ct->Sport);
hw_ct_set_flags(ct, CT_USED | CT_VALID);
/* From now on the entry is valid and it will match packets, but the next still points to it's old copy */
/* Also, there may be packets pending still using the old copy */
hw_ct_set_next(ct, hw_ct_get_next(next));
/* Link software conntrack to new hardware conntrack */
if (next->pCtEntry)
{
PCtEntry twin;
ct->pCtEntry = next->pCtEntry;
ct->pCtEntry->ct = ct;
// Update our twin's twin_dma_addr field
twin = ct->pCtEntry->twin;
if (twin && twin->ct)
twin->ct->twin_dma_addr = ct->dma_addr;
}
/* Unlink old copy from software list and schedule removal */
dlist_remove(&next->list);
hw_ct_schedule_remove(next);
}
}
else
{
dma_pool_free(ctrl->dma_pool, ct, be32_to_cpu(ct->dma_addr));
}
}
}
/** Removes a software conntrack from the local hash and hardware hash
* The hardware conntrack is marked invalid/in use and scheduled to delayed removal.
*
* @param pEntry pointer to the software conntrack
* @param hash hash index where to remove the conntrack
*
*/
static void hw_ct_remove_one(PCtEntry pEntry, U32 hash)
{
struct hw_ct *ct, *prev;
struct pfe_ctrl *ctrl = &pfe->ctrl;
/* Check if there is a hardware conntrack */
if ((ct = pEntry->ct))
{
/* Unlink from software conntrack */
pEntry->ct = NULL;
// printk(KERN_INFO "%s %p\n", __func__, ct);
if (IS_HASH_ARRAY(ctrl, ct))
{
/* Mark invalid but still in use */
/* From this point on, the entry no longer matches but hardware/software still follows pointer
to next entry */
hw_ct_set_flags(ct, CT_USED);
}
else
{
/* Unlink from hardware list */
prev = ct_container(dlist_prev(&ct->list));
hw_ct_set_next(prev, hw_ct_get_next(ct));
/* Unlink from software list */
dlist_remove(&ct->list);
}
hw_ct_schedule_remove(ct);
}
IP_delete_CT_route(pEntry);
}
/** Adds a software conntrack (both directions)
*
* @param pEntry_orig pointer to the originator software conntrack
* @param pEntry_rep pointer to the replier software conntrack
* @param hash_orig hash index where to add the originator conntrack
* @param hash_rep hash index where to add the replier conntrack
*
* @return NO_ERR in case of success, ERR_xxx in case of error
*/
int ct_add(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
int rc;
if ((rc = hw_ct_add_one(pEntry_orig, hash_orig)) != NO_ERR)
goto err0;
if ((rc = hw_ct_add_one(pEntry_rep, hash_rep)) != NO_ERR)
goto err1;
/* Cross-link the two hw entries */
pEntry_orig->ct->twin_dma_addr = pEntry_rep->ct->dma_addr;
pEntry_rep->ct->twin_dma_addr = pEntry_orig->ct->dma_addr;
if(IS_IPV4(pEntry_orig))
{
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_ipv4_link_stats_entry_by_tuple(pEntry_orig, pEntry_orig->Saddr_v4, pEntry_orig->Daddr_v4, pEntry_orig->Sport, pEntry_orig->Dport);
rtpqos_ipv4_link_stats_entry_by_tuple(pEntry_rep, pEntry_rep->Saddr_v4, pEntry_rep->Daddr_v4, pEntry_rep->Sport, pEntry_rep->Dport);
}
else
{
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_ipv6_link_stats_entry_by_tuple(pEntry_orig, pEntry_orig->Saddr_v6, pEntry_orig->Daddr_v6, pEntry_orig->Sport, pEntry_orig->Dport);
rtpqos_ipv6_link_stats_entry_by_tuple(pEntry_rep, pEntry_rep->Saddr_v6, pEntry_rep->Daddr_v6, pEntry_rep->Sport, pEntry_rep->Dport);
}
/* Add to PFE hash atomically */
hw_ct_set_flags(pEntry_orig->ct, CT_USED | CT_VALID);
hw_ct_set_flags(pEntry_rep->ct, CT_USED | CT_VALID);
/* Add to local hash */
slist_add(&ct_cache[hash_orig], &pEntry_orig->list);
slist_add(&ct_cache[hash_rep], &pEntry_rep->list);
#ifdef CFG_STATS
STATISTICS_CTRL_INCREMENT(num_active_connections);
#endif
return NO_ERR;
err1:
hw_ct_remove_one(pEntry_orig, hash_orig);
err0:
L2_route_put(pEntry_orig->tnl_route);
L2_route_put(pEntry_rep->tnl_route);
ct_free(pEntry_orig);
return rc;
}
/** Removes a software conntrack (both directions)
*
* @param pEntry_orig pointer to the originator software conntrack
* @param pEntry_rep pointer to the replier software conntrack
* @param hash_orig hash index where to remove the originator conntrack
* @param hash_rep hash index where to remove the replier conntrack
*
* @return NO_ERR in case of success, ERR_xxx in case of error
*/
void ct_remove(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
L2_route_put(pEntry_orig->tnl_route);
pEntry_orig->tnl_route = NULL;
L2_route_put(pEntry_rep->tnl_route);
pEntry_rep->tnl_route = NULL;
hw_ct_remove_one(pEntry_orig, hash_orig);
hw_ct_remove_one(pEntry_rep, hash_rep);
slist_remove(&ct_cache[hash_orig], &pEntry_orig->list);
slist_remove(&ct_cache[hash_rep], &pEntry_rep->list);
/* Free local entry */
ct_free(pEntry_orig);
#ifdef CFG_STATS
STATISTICS_CTRL_DECREMENT(num_active_connections);
#endif
}
/** Updates a software conntrack
* Updates an hardware conntrack based on an updated software conntrack.
* We assume the hardware conntrack exists already, and update it in place.
*
* @param pEntry pointer to the software conntrack
* @param hash hash index where to update the conntrack
*/
void ct_update_one(PCtEntry pEntry, U32 hash)
{
struct hw_ct *ct = pEntry->ct;
int i;
/* Flag entry as being updated, CLASS software will stop and poll update bit until it's clear */
hw_ct_set_flags(ct, CT_USED | CT_UPDATING | CT_VALID);
/* Update hw entry */
/* 5-tuple can not change, no need to update all fields */
ct->fwmark = cpu_to_be16(pEntry->fwmark);
ct->status = cpu_to_be16(pEntry->status);
if (IS_NULL_ROUTE(pEntry->pRtEntry))
{
IP_Check_Route(pEntry);
}
if (!IS_NULL_ROUTE(pEntry->pRtEntry))
{
ct->route.itf = cpu_to_be32(virt_to_class(pEntry->pRtEntry->itf));
memcpy(ct->route.dstmac, pEntry->pRtEntry->dstmac, ETHER_ADDR_LEN);
ct->route.mtu = cpu_to_be16(pEntry->pRtEntry->mtu);
//ct->route.flags = pEntry->pRtEntry->flags;
ct->route.Daddr_v4= pEntry->pRtEntry->Daddr_v4;
}
else
ct->route.itf = 0xffffffff;
if (!IS_NULL_ROUTE(pEntry->tnl_route))
{
if(pEntry->status & CONNTRACK_4O6)
{
ct->tnl4o6_route.itf = cpu_to_be32(virt_to_class(pEntry->tnl_route->itf));
memcpy(ct->tnl4o6_route.dstmac, pEntry->tnl_route->dstmac, ETHER_ADDR_LEN);
ct->tnl4o6_route.mtu = cpu_to_be16(pEntry->tnl_route->mtu);
// ct->tnl_route.flags = pEntry->tnl_route->flags;
ct->tnl4o6_route.Daddr_v6[0] = pEntry->tnl_route->Daddr_v6[0];
ct->tnl4o6_route.Daddr_v6[1] = pEntry->tnl_route->Daddr_v6[1];
ct->tnl4o6_route.Daddr_v6[2] = pEntry->tnl_route->Daddr_v6[2];
ct->tnl4o6_route.Daddr_v6[3] = pEntry->tnl_route->Daddr_v6[3];
}
else
{
ct->tnl6o4_route.itf = cpu_to_be32(virt_to_class(pEntry->tnl_route->itf));
memcpy(ct->tnl6o4_route.dstmac, pEntry->tnl_route->dstmac, ETHER_ADDR_LEN);
ct->tnl6o4_route.mtu = cpu_to_be16(pEntry->tnl_route->mtu);
ct->tnl6o4_route.Daddr_v4 = pEntry->tnl_route->Daddr_v4;
}
}
else
{
if(pEntry->status & CONNTRACK_IPv6)
ct->tnl6o4_route.itf = 0xffffffff;
else
ct->tnl4o6_route.itf = 0xffffffff;
}
for (i = 0; i < SA_MAX_OP; i++)
{
ct->hSAEntry[i] = cpu_to_be16(pEntry->hSAEntry[i]);
}
hw_ct_set_flags(ct, CT_USED | CT_VALID);
}
/** Updates a software conntrack (both directions)
*
* @param pEntry_orig pointer to the originator software conntrack
* @param pEntry_rep pointer to the replier software conntrack
* @param hash_orig hash index where to remove the originator conntrack
* @param hash_rep hash index where to remove the replier conntrack
*/
void ct_update(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
ct_update_one(pEntry_orig, hash_orig);
ct_update_one(pEntry_rep, hash_rep);
}
#else
PCtEntry ct_alloc(void)
{
PCtEntry pEntry_orig;
pEntry_orig = Heap_Alloc(CTENTRY_BIDIR_SIZE);
if (pEntry_orig)
memset(pEntry_orig, 0, CTENTRY_BIDIR_SIZE);
return pEntry_orig;
}
void ct_free(PCtEntry pEntry_orig)
{
Heap_Free(pEntry_orig);
}
int ct_add(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
slist_add(&ct_cache[hash_orig], &pEntry_orig->list);
slist_add(&ct_cache[hash_rep], &pEntry_rep->list);
#ifdef CFG_STATS
STATISTICS_CTRL_INCREMENT(num_active_connections);
#endif
if(IS_IPV4(pEntry_orig))
{
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_ipv4_link_stats_entry_by_tuple(pEntry_orig, pEntry_orig->Saddr_v4, pEntry_orig->Daddr_v4, pEntry_orig->Sport, pEntry_orig->Dport);
rtpqos_ipv4_link_stats_entry_by_tuple(pEntry_rep, pEntry_rep->Saddr_v4, pEntry_rep->Daddr_v4, pEntry_rep->Sport, pEntry_rep->Dport);
}
else
{
/* check if rtp stats entry is created for this conntrack, if found link the two object and mark the conntrack 's status field for RTP stats */
rtpqos_ipv6_link_stats_entry_by_tuple(pEntry_orig, pEntry_orig->Saddr_v6, pEntry_orig->Daddr_v6, pEntry_orig->Sport, pEntry_orig->Dport);
rtpqos_ipv6_link_stats_entry_by_tuple(pEntry_rep, pEntry_rep->Saddr_v6, pEntry_rep->Daddr_v6, pEntry_rep->Sport, pEntry_rep->Dport);
}
return NO_ERR;
}
void ct_remove(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
IP_delete_CT_route(pEntry_orig);
IP_delete_CT_route(pEntry_rep);
L2_route_put(pEntry_orig->tnl_route);
pEntry_orig->tnl_route = NULL;
L2_route_put(pEntry_rep->tnl_route);
pEntry_rep->tnl_route = NULL;
slist_remove(&ct_cache[hash_rep], &pEntry_rep->list);
slist_remove(&ct_cache[hash_orig], &pEntry_orig->list);
CTData_Free(pEntry_orig);
#ifdef CFG_STATS
STATISTICS_CTRL_DECREMENT(num_active_connections);
#endif
}
void ct_update(PCtEntry pEntry_orig, PCtEntry pEntry_rep, U32 hash_orig, U32 hash_rep)
{
return;
}
#endif
/**
* IP_delete_CT_route()
*
*
*/
void IP_delete_CT_route(PCtEntry pEntry)
{
PRouteEntry pRtEntry = pEntry->pRtEntry;
U32 id;
if (IS_NULL_ROUTE(pRtEntry))
return;
id = pRtEntry->id;
L2_route_put(pRtEntry);
#if defined(COMCERTO_2000)
pEntry->pRtEntry = NULL;
#else
pEntry->route_id = id;
#endif
}
PRouteEntry IP_Check_Route(PCtEntry pCtEntry)
{
PRouteEntry pRtEntry;
pRtEntry = L2_route_get(pCtEntry->route_id);
if (!pRtEntry)
return NULL;
pCtEntry->pRtEntry = pRtEntry;
return pRtEntry;
}
/**
* IP_expire()
*
*
*/
void IP_expire(PCtEntry pCtEntry)
{
/* Can't send indication, try later from timeout routine */
pCtEntry->status |= (CONNTRACK_FF_DISABLED | CONNTRACK_TIMED_OUT);
IP_delete_CT_route(pCtEntry);
}
U32 IP_get_fwmark(PCtEntry pOrigEntry, PCtEntry pReplEntry)
{
U32 fwmark;
fwmark = pOrigEntry->fwmark;
if (fwmark != pReplEntry->fwmark)
{
fwmark |= ((U32)pReplEntry->fwmark << 16) | 0x80000000;
}
return fwmark;
}
U32 Get_Ctentry_Hash(PVOID pblock)
{
PCtEntry pctentry = pblock;
U32 hash;
U32 protocol;
protocol = GET_PROTOCOL(pctentry);
if (IS_IPV4(pblock))
{
hash = HASH_CT(pctentry->Saddr_v4, pctentry->Daddr_v4, pctentry->Sport, pctentry->Dport, protocol);
}
else
{
PCtEntryIPv6 pctentry6 = pblock;
hash = HASH_CT6(pctentry6->Saddr_v6, pctentry6->Daddr_v6, pctentry6->Sport, pctentry6->Dport, protocol);
}
return hash;
}
int IPv4_delete_CTpair(PCtEntry ctEntry)
{
PCtEntry twin_entry;
struct _tCtCommand *message;
HostMessage *pmsg;
twin_entry = CT_TWIN(ctEntry);
if ((twin_entry->status & CONNTRACK_ORIG) == CONNTRACK_ORIG)
{
ctEntry = twin_entry;
twin_entry = CT_TWIN(ctEntry);
}
// Send indication message
pmsg = msg_alloc();
if (!pmsg)
goto err;
message = (struct _tCtCommand *)pmsg->data;
// Prepare indication message
message->action = (ctEntry->status & CONNTRACK_TCP_FIN) ? ACTION_TCP_FIN : ACTION_REMOVED;
message->Saddr= ctEntry->Saddr_v4;
message->Daddr= ctEntry->Daddr_v4;
message->Sport= ctEntry->Sport;
message->Dport= ctEntry->Dport ;
message->SaddrReply= ctEntry->twin_Saddr;
message->DaddrReply= ctEntry->twin_Daddr;
message->SportReply= ctEntry->twin_Sport;
message->DportReply= ctEntry->twin_Dport;
message->protocol= GET_PROTOCOL(ctEntry);
message->fwmark = 0;
pmsg->code = CMD_IPV4_CONNTRACK_CHANGE;
pmsg->length = sizeof(*message);
if (msg_send(pmsg) < 0)
goto err;
//Remove conntrack from list
ct_remove(ctEntry, twin_entry, Get_Ctentry_Hash(ctEntry), Get_Ctentry_Hash(twin_entry));
return 0;
err:
/* Can't send indication, try later from timeout routine */
IP_expire(ctEntry);
IP_expire(twin_entry);
return 1;
}
/**
* IP_ct_timer
*
*/
static void IP_ct_timer(unsigned int start, unsigned int end)
{
PCtEntry pCtEntry;
PCtEntry twin_entry;
U32 i;
U32 timer;
U32 twin_timer;
U32 orig_timer;
struct slist_entry *entry;
#if defined(COMCERTO_2000)
hw_ct_delayed_remove();
#endif
for (i = start; i < end; i++)
{
restart_loop:
slist_for_each(pCtEntry, entry, &ct_cache[i], list)
{
if ((pCtEntry->status & CONNTRACK_ORIG) == CONNTRACK_ORIG) // only check orig ctEntries (not their twins)
{
BOOL bidir_flag;
twin_entry = CT_TWIN(pCtEntry);
#if defined(COMCERTO_2000)
if (ff_enable)
{
struct hw_ct *ct;
if ((ct = pCtEntry->ct) && hw_ct_get_active(ct))
{
pCtEntry->last_ct_timer = ct_timer;
hw_ct_set_active(ct, 0);
}
if ((ct = twin_entry->ct) && hw_ct_get_active(ct))
{
twin_entry->last_ct_timer = ct_timer;
hw_ct_set_active(ct, 0);
}
}
#endif
orig_timer = timer = ct_timer - pCtEntry->last_ct_timer;
twin_timer = ct_timer - twin_entry->last_ct_timer;
bidir_flag = twin_entry->last_ct_timer != UDP_REPLY_TIMER_INIT;
if (bidir_flag)
{
if (twin_timer < timer)
timer = twin_timer;
}
if (timer > GET_TIMEOUT_VALUE(pCtEntry , bidir_flag)
|| (pCtEntry->status & CONNTRACK_TIMED_OUT))
{
if (IS_IPV4(pCtEntry))
{
if (!IPv4_delete_CTpair(pCtEntry))
goto restart_loop;
}
else
{
if (!IPv6_delete_CTpair((PCtEntryIPv6)pCtEntry))
goto restart_loop;
}
}
#if !defined(COMCERTO_2000)
// Detach from route (and try to move it DDR) if no longer in use
if (orig_timer >= CT_INACTIVE_TIME)
{
IP_delete_CT_route(pCtEntry);
}
if (bidir_flag && (twin_timer >= CT_INACTIVE_TIME))
{
IP_delete_CT_route(twin_entry);
}
// check for inactive connection
if (timer >= CT_INACTIVE_TIME)
{
// if CT is in ARAM, move it to DDR
if (Is_FastCT_block(pCtEntry))
{
// Move entry to DDR
PVOID newblock;
newblock = Heap_Alloc(CTENTRY_BIDIR_SIZE);
if (newblock != NULL)
{
Fast_CTData_CopyBlock(newblock, pCtEntry);
CTData_Free(pCtEntry);
pCtEntry = (PCtEntry)(newblock);
if(pCtEntry->status & CONNTRACK_RTP_STATS) {
PCtEntryIPv6 pCT6Entry;
if (IS_IPV4(pCtEntry)) {
/* check if rtp stats entry is created for this conntrack, if found link the two object */
rtpqos_ipv4_link_stats_entry_by_tuple(pCtEntry, pCtEntry->Saddr_v4, pCtEntry->Daddr_v4, pCtEntry->Sport, pCtEntry->Dport);
} else {
pCT6Entry = (PCtEntryIPv6)pCtEntry;
rtpqos_ipv6_link_stats_entry_by_tuple((void *)pCT6Entry, pCT6Entry->Saddr_v6, pCT6Entry->Daddr_v6, pCT6Entry->Sport, pCT6Entry->Dport);
}
}
twin_entry = CT_TWIN(pCtEntry); // need to re-define after moving pCtEntry
if(twin_entry->status & CONNTRACK_RTP_STATS) {
PCtEntryIPv6 twin6_entry;
if (IS_IPV4(twin_entry)) {
/* check if rtp stats entry is created for this conntrack, if found link the two object */
rtpqos_ipv4_link_stats_entry_by_tuple(twin_entry, twin_entry->Saddr_v4, twin_entry->Daddr_v4, twin_entry->Sport, twin_entry->Dport);
} else {
twin6_entry = (PCtEntryIPv6)twin_entry;
rtpqos_ipv6_link_stats_entry_by_tuple((void *)twin6_entry, twin6_entry->Saddr_v6, twin6_entry->Daddr_v6, twin6_entry->Sport, twin6_entry->Dport);
}
}
}
}
}
#endif /* !defined(COMCERTO_2000) */
}
}
}
}
static void M_ip_timer(void)
{
unsigned int start, end;
ct_timer++;
/* check active connection and send keep alive to CSP if needed */
start = ct_bin_start;
end = start + CT_TIMER_BINSIZE;
if (end >= NUM_CT_ENTRIES)
{
end = NUM_CT_ENTRIES;
ct_bin_start = 0;
}
else
ct_bin_start = end;
if (ff_enable)
IP_ct_timer(start, end);
}
void IP_deleteCt_from_onif_index(U32 if_index)
{
int i;
int rc;
PCtEntry pCtEntry;
struct slist_entry *entry;
for (i = 0; i < NUM_CT_ENTRIES; i++)
{
restart_loop:
slist_for_each_safe(pCtEntry, entry, &ct_cache[i], list)
{
/* Check the conntrack entry matching with the corresponding Rtentry */
if(!(pCtEntry->status & CONNTRACK_TIMED_OUT) && !IS_NULL_ROUTE(pCtEntry->pRtEntry))
{
PRouteEntry pRtEntry = pCtEntry->pRtEntry;
if (pRtEntry->itf->index == if_index)
{
if (IS_IPV4(pCtEntry))
rc = IPv4_delete_CTpair(pCtEntry);
else
rc = IPv6_delete_CTpair((PCtEntryIPv6)pCtEntry);
if (!rc)
goto restart_loop;
}
}
}
}
}
static PCtEntry IPv4_find_ctentry(U32 hash, U32 saddr, U32 daddr, U16 sport, U16 dport)
{
PCtEntry pEntry;
struct slist_entry *entry;
slist_for_each(pEntry, entry, &ct_cache[hash], list)
{
// Note: we don't need to check for protocol match, since if all of the other fields match then the
// protocol will always match.
if (IS_IPV4(pEntry) && pEntry->Saddr_v4 == saddr && pEntry->Daddr_v4 == daddr && pEntry->Sport == sport && pEntry->Dport == dport)
return pEntry;
}
return NULL;
}
PCtEntry IPv4_get_ctentry(U32 saddr, U32 daddr, U16 sport, U16 dport, U16 proto)
{
U32 hash;
hash = HASH_CT(saddr, daddr, sport, dport, proto);
return IPv4_find_ctentry(hash, saddr, daddr, sport, dport);
}
int IPv4_HandleIP_CONNTRACK(U16 *p, U16 Length)
{
PCtEntry pEntry_orig = NULL, pEntry_rep = NULL;
CtExCommand Ctcmd;
int i, reset_action = 0;
U32 sum;
U32 tmpU32;
U16 hash_key_ct_orig, hash_key_ct_rep;
PCtExCommand pCtCmd;
// Check length
if ((Length != sizeof(CtCommand)) && (Length != sizeof(CtExCommand)))
return ERR_WRONG_COMMAND_SIZE;
// Ensure alignment
SFL_memcpy((U8*)&Ctcmd, (U8*)p, Length);
hash_key_ct_orig = HASH_CT(Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport, Ctcmd.protocol);
hash_key_ct_rep = HASH_CT(Ctcmd.SaddrReply, Ctcmd.DaddrReply, Ctcmd.SportReply, Ctcmd.DportReply, Ctcmd.protocol);
switch(Ctcmd.action)
{
case ACTION_DEREGISTER:
pEntry_orig = IPv4_find_ctentry(hash_key_ct_orig, Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport);
pEntry_rep = IPv4_find_ctentry(hash_key_ct_rep, Ctcmd.SaddrReply, Ctcmd.DaddrReply, Ctcmd.SportReply, Ctcmd.DportReply);
if (pEntry_orig == NULL || pEntry_rep == NULL ||
CT_TWIN(pEntry_orig) != pEntry_rep || CT_TWIN(pEntry_rep) != pEntry_orig ||
((pEntry_orig->status & CONNTRACK_ORIG) != CONNTRACK_ORIG))
return ERR_CT_ENTRY_NOT_FOUND;
ct_remove(pEntry_orig, pEntry_rep, hash_key_ct_orig, hash_key_ct_rep);
break;
case ACTION_REGISTER:
/* We first check any possible errors case in the register request (already existing entries, route or arp not found...)
then conntract entries allocations is performed */
pEntry_orig = IPv4_find_ctentry(hash_key_ct_orig, Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport);
pEntry_rep = IPv4_find_ctentry(hash_key_ct_rep, Ctcmd.SaddrReply, Ctcmd.DaddrReply, Ctcmd.SportReply, Ctcmd.DportReply);
if (pEntry_orig != NULL && pEntry_rep != NULL && ((pEntry_orig->status & CONNTRACK_ORIG) != CONNTRACK_ORIG))
return ERR_CREATION_FAILED; // Reverse entry already exists
if (pEntry_orig != NULL || pEntry_rep != NULL)
return ERR_CT_ENTRY_ALREADY_REGISTERED; //trying to add exactly the same conntrack
if (Ctcmd.format & CT_SECURE) {
if (Ctcmd.SA_nr > SA_MAX_OP)
return ERR_CT_ENTRY_TOO_MANY_SA_OP;
for (i=0;i<Ctcmd.SA_nr;i++) {
if (M_ipsec_sa_cache_lookup_by_h(Ctcmd.SA_handle[i]) == NULL)
return ERR_CT_ENTRY_INVALID_SA;
}
if (Ctcmd.SAReply_nr > SA_MAX_OP)
return ERR_CT_ENTRY_TOO_MANY_SA_OP;
for (i=0;i<Ctcmd.SAReply_nr;i++) {
if (M_ipsec_sa_cache_lookup_by_h(Ctcmd.SAReply_handle[i]) == NULL)
return ERR_CT_ENTRY_INVALID_SA;
}
}
/* originator ------------------------------------*/
if ((pEntry_orig = ct_alloc()) == NULL)
{
return ERR_NOT_ENOUGH_MEMORY;
}
pEntry_orig->Daddr_v4 = Ctcmd.Daddr;
pEntry_orig->Saddr_v4 = Ctcmd.Saddr;
pEntry_orig->Sport = Ctcmd.Sport;
pEntry_orig->Dport = Ctcmd.Dport;
pEntry_orig->twin_Daddr = Ctcmd.DaddrReply;
pEntry_orig->twin_Saddr = Ctcmd.SaddrReply;
pEntry_orig->twin_Sport = Ctcmd.SportReply;
pEntry_orig->twin_Dport = Ctcmd.DportReply;
pEntry_orig->last_ct_timer = ct_timer;
pEntry_orig->fwmark = Ctcmd.fwmark & 0xFFFF;
pEntry_orig->status = CONNTRACK_ORIG;
if (Ctcmd.flags & CTCMD_FLAGS_ORIG_DISABLED)
pEntry_orig->status |= CONNTRACK_FF_DISABLED;
pEntry_orig->route_id = Ctcmd.route_id;
if (Ctcmd.format & CT_SECURE) {
pEntry_orig->status |= CONNTRACK_SEC;
for (i=0;i < SA_MAX_OP;i++)
pEntry_orig->hSAEntry[i] =
(i<Ctcmd.SA_nr) ? Ctcmd.SA_handle[i] : 0;
if (pEntry_orig->hSAEntry[0])
pEntry_orig->status &= ~ CONNTRACK_SEC_noSA;
else
pEntry_orig->status |= CONNTRACK_SEC_noSA;
}
/* Replier ----------------------------------------*/
pEntry_rep = CT_TWIN(pEntry_orig);
pEntry_rep->Daddr_v4 = Ctcmd.DaddrReply;
pEntry_rep->Saddr_v4 = Ctcmd.SaddrReply;
pEntry_rep->Sport = Ctcmd.SportReply;
pEntry_rep->Dport = Ctcmd.DportReply;
pEntry_rep->twin_Daddr = Ctcmd.Daddr;
pEntry_rep->twin_Saddr = Ctcmd.Saddr;
pEntry_rep->twin_Sport = Ctcmd.Sport;
pEntry_rep->twin_Dport = Ctcmd.Dport;
if (Ctcmd.fwmark & 0x80000000)
pEntry_rep->fwmark = ((Ctcmd.fwmark >> 16) & 0x7FFF) | (Ctcmd.fwmark & 0x8000);
else
pEntry_rep->fwmark = pEntry_orig->fwmark;
pEntry_rep->status = 0;
SET_PROTOCOL(pEntry_orig, pEntry_rep, Ctcmd.protocol);
if(Ctcmd.protocol == IPPROTOCOL_UDP)
pEntry_rep->last_ct_timer = UDP_REPLY_TIMER_INIT;
else
pEntry_rep->last_ct_timer = ct_timer;
if (Ctcmd.flags & CTCMD_FLAGS_REP_DISABLED)
pEntry_rep->status |= CONNTRACK_FF_DISABLED;
pEntry_rep->route_id = Ctcmd.route_id_reply;
if (Ctcmd.format & CT_SECURE) {
pEntry_rep->status |= CONNTRACK_SEC;
for (i=0; i < SA_MAX_OP;i++)
pEntry_rep->hSAEntry[i]=
(i<Ctcmd.SAReply_nr) ? Ctcmd.SAReply_handle[i] : 0;
if ( pEntry_rep->hSAEntry[0])
pEntry_rep->status &= ~CONNTRACK_SEC_noSA;
else
pEntry_rep->status |= CONNTRACK_SEC_noSA;
}
pEntry_orig->ip_chksm_corr = 0x0001;
pEntry_rep->ip_chksm_corr = 0x0001;
/* precompute forward processing (NAT or IP forward?) */
if((pEntry_orig->Daddr_v4 != pEntry_rep->Saddr_v4)
|| (pEntry_orig->Sport != pEntry_rep->Dport)
|| (pEntry_orig->Dport != pEntry_rep->Sport )
|| (pEntry_orig->Saddr_v4 != pEntry_rep->Daddr_v4))
{
U32 Daddr_diff = 0;
U32 Saddr_diff = 0;
U32 Sport_diff = 0;
U32 Dport_diff = 0;
/* Check sum correction pre-computation RFC1624 */
/* DNAT ? */
if(pEntry_orig->Daddr_v4 != pEntry_rep->Saddr_v4)
{
Daddr_diff = (pEntry_orig->Daddr_v4 & 0xffff)+
(pEntry_orig->Daddr_v4 >> 16) +
((pEntry_rep->Saddr_v4 & 0xffff) ^ 0xffff) +
((pEntry_rep->Saddr_v4 >>16) ^ 0xffff);
}
/* SNAT ? */
if(pEntry_orig->Saddr_v4 != pEntry_rep->Daddr_v4)
{
Saddr_diff = (pEntry_orig->Saddr_v4 & 0xffff)+
(pEntry_orig->Saddr_v4 >> 16) +
((pEntry_rep->Daddr_v4 & 0xffff) ^ 0xffff) +
((pEntry_rep->Daddr_v4 >>16) ^ 0xffff);
}
/* PDNAT ? */
if(pEntry_orig->Dport != pEntry_rep->Sport)
{
Dport_diff = (pEntry_orig->Dport) +
((pEntry_rep->Sport) ^ 0xffff);
}
/* PSNAT ? */
if(pEntry_orig->Sport != pEntry_rep->Dport)
{
Sport_diff = (pEntry_orig->Sport) +
((pEntry_rep->Dport) ^ 0xffff);
}
/* IP Checksum */
sum = Daddr_diff + Saddr_diff;
while (sum>>16)
sum = (sum & 0xffff)+(sum >> 16);
if (sum == 0xffff)
sum = 0;
tmpU32 = sum + 0x0001;
if (tmpU32 + 1 >= 0x10000) // add in carry, and convert 0xFFFF to 0x0000
tmpU32++;
pEntry_orig->ip_chksm_corr = tmpU32;
/* Replier checksum */
sum = sum == 0 ? 0 : sum ^ 0xffff;
tmpU32 = sum + 0x0001;
if (tmpU32 + 1 >= 0x10000) // add in carry, and convert 0xFFFF to 0x0000
tmpU32++;
pEntry_rep->ip_chksm_corr = tmpU32;
/* UDP/TCP checksum */
sum = Daddr_diff + Saddr_diff + Dport_diff + Sport_diff;
while (sum>>16)
sum = (sum & 0xffff)+(sum >> 16);
if (sum == 0xffff)
sum = 0;
pEntry_orig->tcp_udp_chksm_corr = sum;
/* Replier checksum */
pEntry_rep->tcp_udp_chksm_corr = sum == 0 ? 0 : sum ^ 0xffff;
/* Set status */
pEntry_orig->status |= CONNTRACK_NAT;
pEntry_rep->status |= CONNTRACK_NAT;
}
if ((Ctcmd.format & CT_ORIG_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_ORIG_DISABLED))
{
pEntry_orig->tnl_route = L2_route_get(Ctcmd.tunnel_route_id);
if (IS_NULL_ROUTE(pEntry_orig->tnl_route))
{
ct_free((PCtEntry)pEntry_orig);
return ERR_RT_LINK_NOT_POSSIBLE;
}
pEntry_orig->status |= CONNTRACK_4O6;
}
if ((Ctcmd.format & CT_REPL_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_REP_DISABLED))
{
pEntry_rep->tnl_route = L2_route_get(Ctcmd.tunnel_route_id_reply);
if (IS_NULL_ROUTE(pEntry_rep->tnl_route))
{
L2_route_put(pEntry_orig->tnl_route);
ct_free((PCtEntry)pEntry_orig);
return ERR_RT_LINK_NOT_POSSIBLE;
}
pEntry_rep->status |= CONNTRACK_4O6;
}
/* Everything went Ok. We can safely put querier and replier entries in hash tables */
/* add orig+replier to head of hash table list */
return ct_add(pEntry_orig, pEntry_rep, hash_key_ct_orig, hash_key_ct_rep);
case ACTION_UPDATE:
pEntry_orig = IPv4_find_ctentry(hash_key_ct_orig, Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport);
pEntry_rep = IPv4_find_ctentry(hash_key_ct_rep, Ctcmd.SaddrReply, Ctcmd.DaddrReply, Ctcmd.SportReply, Ctcmd.DportReply);
if (pEntry_orig == NULL || pEntry_rep == NULL ||
CT_TWIN(pEntry_orig) != pEntry_rep || CT_TWIN(pEntry_rep) != pEntry_orig ||
((pEntry_orig->status & CONNTRACK_ORIG) != CONNTRACK_ORIG))
return ERR_CT_ENTRY_NOT_FOUND;
if ((Ctcmd.format & CT_ORIG_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_ORIG_DISABLED))
{
PRouteEntry tnl_route;
tnl_route = L2_route_get(Ctcmd.tunnel_route_id);
if (IS_NULL_ROUTE(tnl_route))
return ERR_RT_LINK_NOT_POSSIBLE;
L2_route_put(tnl_route);
}
if ((Ctcmd.format & CT_REPL_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_REP_DISABLED))
{
PRouteEntry tnl_route;
tnl_route = L2_route_get(Ctcmd.tunnel_route_id_reply);
if (IS_NULL_ROUTE(tnl_route))
return ERR_RT_LINK_NOT_POSSIBLE;
L2_route_put(tnl_route);
}
if (Ctcmd.format & CT_SECURE) {
if (Ctcmd.SA_nr > SA_MAX_OP)
return ERR_CT_ENTRY_TOO_MANY_SA_OP;
for (i = 0; i < Ctcmd.SA_nr; i++) {
if (pEntry_orig->hSAEntry[i] != Ctcmd.SA_handle[i])
if (M_ipsec_sa_cache_lookup_by_h( Ctcmd.SA_handle[i]) == NULL)
return ERR_CT_ENTRY_INVALID_SA;
}
if (Ctcmd.SAReply_nr > SA_MAX_OP)
return ERR_CT_ENTRY_TOO_MANY_SA_OP;
for (i = 0; i < Ctcmd.SAReply_nr; i++) {
if (pEntry_rep->hSAEntry[i] != Ctcmd.SAReply_handle[i])
if (M_ipsec_sa_cache_lookup_by_h(Ctcmd.SAReply_handle[i]) == NULL)
return ERR_CT_ENTRY_INVALID_SA;
}
}
pEntry_orig->fwmark = Ctcmd.fwmark & 0xFFFF;
if (Ctcmd.flags & CTCMD_FLAGS_ORIG_DISABLED) {
pEntry_orig->status |= CONNTRACK_FF_DISABLED;
IP_delete_CT_route(pEntry_orig);
} else
pEntry_orig->status &= ~CONNTRACK_FF_DISABLED;
if (Ctcmd.format & CT_SECURE) {
pEntry_orig->status |= CONNTRACK_SEC;
for (i = 0;i < SA_MAX_OP; i++)
pEntry_orig->hSAEntry[i] =
(i<Ctcmd.SA_nr) ? Ctcmd.SA_handle[i] : 0;
if (pEntry_orig->hSAEntry[0])
pEntry_orig->status &= ~ CONNTRACK_SEC_noSA;
else
pEntry_orig->status |= CONNTRACK_SEC_noSA;
} else
pEntry_orig->status &= ~CONNTRACK_SEC;
if (Ctcmd.fwmark & 0x80000000)
pEntry_rep->fwmark = ((Ctcmd.fwmark >> 16) & 0x7FFF) | (Ctcmd.fwmark & 0x8000);
else
pEntry_rep->fwmark = pEntry_orig->fwmark;
if (Ctcmd.flags & CTCMD_FLAGS_REP_DISABLED) {
pEntry_rep->status |= CONNTRACK_FF_DISABLED;
IP_delete_CT_route(pEntry_rep);
} else
pEntry_rep->status &= ~CONNTRACK_FF_DISABLED;
if (Ctcmd.format & CT_SECURE) {
pEntry_rep->status |= CONNTRACK_SEC;
for (i = 0; i < SA_MAX_OP; i++)
pEntry_rep->hSAEntry[i]=
(i<Ctcmd.SAReply_nr) ? Ctcmd.SAReply_handle[i] : 0;
if ( pEntry_rep->hSAEntry[0])
pEntry_rep->status &= ~CONNTRACK_SEC_noSA;
else
pEntry_rep->status |= CONNTRACK_SEC_noSA;
} else
pEntry_rep->status &= ~CONNTRACK_SEC;
/* Update route entries if needed */
if (IS_NULL_ROUTE(pEntry_orig->pRtEntry))
{
pEntry_orig->route_id = Ctcmd.route_id;
}
else if (pEntry_orig->pRtEntry->id != Ctcmd.route_id)
{
IP_delete_CT_route(pEntry_orig);
pEntry_orig->route_id = Ctcmd.route_id;
}
if (IS_NULL_ROUTE(pEntry_rep->pRtEntry))
{
pEntry_rep->route_id = Ctcmd.route_id_reply;
}
else if (pEntry_rep->pRtEntry->id != Ctcmd.route_id_reply)
{
IP_delete_CT_route(pEntry_rep);
pEntry_rep->route_id = Ctcmd.route_id_reply;
}
if (pEntry_orig->tnl_route)
{
L2_route_put(pEntry_orig->tnl_route);
pEntry_orig->tnl_route = NULL;
pEntry_orig->status &= ~CONNTRACK_4O6;
}
if ((Ctcmd.format & CT_ORIG_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_ORIG_DISABLED))
{
pEntry_orig->tnl_route = L2_route_get(Ctcmd.tunnel_route_id);
pEntry_orig->status |= CONNTRACK_4O6;
}
if (pEntry_rep->tnl_route)
{
L2_route_put(pEntry_rep->tnl_route);
pEntry_rep->tnl_route = NULL;
pEntry_rep->status &= ~CONNTRACK_4O6;
}
if ((Ctcmd.format & CT_REPL_TUNNEL_4O6) && !(Ctcmd.flags & CTCMD_FLAGS_REP_DISABLED))
{
pEntry_rep->tnl_route = L2_route_get(Ctcmd.tunnel_route_id_reply);
pEntry_rep->status |= CONNTRACK_4O6;
}
ct_update(pEntry_orig, pEntry_rep, hash_key_ct_orig, hash_key_ct_rep);
return NO_ERR;
case ACTION_QUERY:
reset_action = 1;
case ACTION_QUERY_CONT:
{
int rc;
pCtCmd = (PCtExCommand)p;
rc = IPv4_Get_Next_Hash_CTEntry(pCtCmd, reset_action);
return rc;
}
default :
return ERR_UNKNOWN_ACTION;
}
return NO_ERR;
}
int IP_HandleIP_ROUTE_RESOLVE (U16 *p, U16 Length)
{
PRouteEntry pRtEntry;
RtCommand RtCmd;
PRtCommand pRtCmd;
int rc = NO_ERR, reset_action = 0;
POnifDesc onif_desc;
// Check length
if (Length != sizeof(RtCommand))
return ERR_WRONG_COMMAND_SIZE;
// Ensure alignment
SFL_memcpy((U8*)&RtCmd, (U8*)p, sizeof(RtCommand));
pRtEntry = L2_route_find(RtCmd.id);
switch(RtCmd.action)
{
case ACTION_REGISTER:
if (pRtEntry)
return ERR_RT_ENTRY_ALREADY_REGISTERED; //trying to add exactly the same route
onif_desc = get_onif_by_name(RtCmd.outputDevice);
if (!onif_desc)
return ERR_UNKNOWN_INTERFACE;
/* This is no longer required for C2K but for C1K inorder to
acoomodate most routes in the ARAM, the size of the route
entry needs to be conditionally increased.*/
#if !defined(COMCERTO_2000)
if (RtCmd.flags & (RTCMD_FLAGS_6o4 | RTCMD_FLAGS_4o6))
pRtEntry = L2_route_add(RtCmd.id,16);
else
#endif
pRtEntry = L2_route_add(RtCmd.id, 0);
if (!pRtEntry)
return ERR_NOT_ENOUGH_MEMORY;
pRtEntry->itf = onif_desc->itf;
if (pRtEntry->itf->type & IF_TYPE_PPPOE)
COPY_MACADDR(pRtEntry->dstmac, ((pPPPoE_Info)pRtEntry->itf)->DstMAC);
else
COPY_MACADDR(pRtEntry->dstmac, RtCmd.macAddr);
rte_set_mtu(pRtEntry, RtCmd.mtu);
if (RtCmd.flags & RTCMD_FLAGS_6o4)
*((U32 *)ROUTE_EXTRA_INFO(pRtEntry)) = RtCmd.daddr[0];
else if (RtCmd.flags & RTCMD_FLAGS_4o6)
SFL_memcpy(ROUTE_EXTRA_INFO(pRtEntry), RtCmd.daddr, IPV6_ADDRESS_LENGTH);
break;
case ACTION_UPDATE:
if (!pRtEntry)
return ERR_RT_ENTRY_NOT_FOUND;
onif_desc = get_onif_by_name(RtCmd.outputDevice);
if (!onif_desc)
return ERR_UNKNOWN_INTERFACE;
pRtEntry->itf = onif_desc->itf;
if (pRtEntry->itf->type & IF_TYPE_PPPOE)
COPY_MACADDR(pRtEntry->dstmac, ((pPPPoE_Info)pRtEntry->itf)->DstMAC);
else
COPY_MACADDR(pRtEntry->dstmac, RtCmd.macAddr);
rte_set_mtu(pRtEntry, RtCmd.mtu);
break;
case ACTION_DEREGISTER:
rc = L2_route_remove(RtCmd.id);
break;
case ACTION_QUERY:
reset_action = 1;
/* fallthrough */
case ACTION_QUERY_CONT:
pRtCmd = (PRtCommand)p;
rc = IPV4_Get_Next_Hash_RtEntry(pRtCmd, reset_action);
break;
default:
rc = ERR_UNKNOWN_ACTION;
break;
}
return rc;
}
static int IPv4_HandleIP_RESET (void)
{
PRouteEntry pRtEntry = NULL;
int i;
int rc = NO_ERR;
/* free Conntrack entries -- this handles both IPv4 and IPv6 */
for(i = 0; i < NUM_CT_ENTRIES; i++)
{
PCtEntry pEntry_orig, pEntry_rep;
struct slist_entry *entry;
while ((entry = slist_first(&ct_cache[i])) != NULL)
{
pEntry_orig = CT_ORIG(container_of(entry, CtEntry, list));
if (IS_IPV4(pEntry_orig))
pEntry_rep = CT_TWIN(pEntry_orig);
else
pEntry_rep = (PCtEntry)CT6_TWIN(pEntry_orig);
ct_remove(pEntry_orig, pEntry_rep, Get_Ctentry_Hash(pEntry_orig), Get_Ctentry_Hash(pEntry_rep));
}
}
/* free IPv4 sockets entries */
SOCKET4_free_entries();
/* Do IPv6 reset */
IPv6_handle_RESET();
/* free all Route entries */
for(i = 0; i < NUM_ROUTE_ENTRIES; i++)
{
struct slist_entry *entry;
slist_for_each_safe(pRtEntry, entry, &rt_cache[i], list)
{
L2_route_remove(pRtEntry->id);
}
}
#if !defined(COMCERTO_2000_CONTROL)
memset(sockid_cache, 0, sizeof(PVOID) * NUM_SOCK_ENTRIES);
#endif
return rc;
}
static int IPv4_HandleIP_SET_TIMEOUT (U16 *p, U16 Length)
{
TimeoutCommand TimeoutCmd;
int rc = NO_ERR;
// Check length
if (Length != sizeof(TimeoutCommand))
return ERR_WRONG_COMMAND_SIZE;
// Ensure alignment
SFL_memcpy((U8*)&TimeoutCmd, (U8*)p, sizeof(TimeoutCommand));
// Check protocol and update timeout value
switch(TimeoutCmd.protocol)
{
case IPPROTOCOL_TCP:
if(TimeoutCmd.sam_4o6_timeout)
tcp_4o6_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
else
tcp_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
break;
case IPPROTOCOL_UDP:
if(TimeoutCmd.sam_4o6_timeout)
{
udp_4o6_bidir_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
udp_4o6_unidir_timeout = (TimeoutCmd.timeout_value2 > 0 ? TimeoutCmd.timeout_value2 : TimeoutCmd.timeout_value1) * CT_TICKS_PER_SECOND;
}
else
{
udp_bidir_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
udp_unidir_timeout = (TimeoutCmd.timeout_value2 > 0 ? TimeoutCmd.timeout_value2 : TimeoutCmd.timeout_value1) * CT_TICKS_PER_SECOND;
}
break;
#define UNKNOWN_PROTO 0
case UNKNOWN_PROTO:
if(TimeoutCmd.sam_4o6_timeout)
other_4o6_proto_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
else
other_proto_timeout = TimeoutCmd.timeout_value1 * CT_TICKS_PER_SECOND;
break;
default:
rc = ERR_UNKNOWN_ACTION;
break;
}
return rc;
}
static int IPv4_HandleIP_Get_Timeout(U16 *p, U16 Length)
{
int rc = NO_ERR;
PTimeoutCommand TimeoutCmd;
CtCommand Ctcmd;
U32 hash_key_ct_orig;
PCtEntry pEntry, twin_entry;
// Check length
if (Length != sizeof(CtCommand))
return ERR_WRONG_COMMAND_SIZE;
// Ensure alignment
SFL_memcpy((U8*)&Ctcmd, (U8*)p, Length);
hash_key_ct_orig = HASH_CT(Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport, Ctcmd.protocol);
if ((pEntry = IPv4_find_ctentry(hash_key_ct_orig, Ctcmd.Saddr, Ctcmd.Daddr, Ctcmd.Sport, Ctcmd.Dport)) != NULL)
{
BOOL bidir_flag;
int timeout_value;
U32 current_timer, twin_timer;
memset(p, 0, 256);
TimeoutCmd = (PTimeoutCommand)(p+1);
TimeoutCmd->protocol = (pEntry->status & CONNTRACK_UDP) ? IPPROTOCOL_UDP : IPPROTOCOL_TCP;
twin_entry = CT_TWIN(pEntry);
current_timer = ct_timer - pEntry->last_ct_timer;
bidir_flag = twin_entry->last_ct_timer != UDP_REPLY_TIMER_INIT;
if (bidir_flag)
{
twin_timer = ct_timer - twin_entry->last_ct_timer;
if (twin_timer < current_timer)
current_timer = twin_timer;
}
timeout_value = GET_TIMEOUT_VALUE(pEntry, bidir_flag) - current_timer;
if (timeout_value < 0)
timeout_value = 0;
TimeoutCmd->timeout_value1 = (U32)timeout_value / CT_TICKS_PER_SECOND;
}
else
{
return CMD_ERR;
}
return rc;
}
static int IPv4_HandleIP_FF_CONTROL (U16 *p, U16 Length)
{
int rc = NO_ERR;
FFControlCommand FFControlCmd;
// Check length
if (Length != sizeof(FFControlCommand))
return ERR_WRONG_COMMAND_SIZE;
// Ensure alignment
SFL_memcpy((U8*)&FFControlCmd, (U8*)p, sizeof(FFControlCommand));
if(FFControlCmd.enable == 1){
PCtEntry ct;
int i;
ff_enable = 1;
/* Reset all timeouts */
for(i = 0; i < NUM_CT_ENTRIES; i++)
{
struct slist_entry *entry;
slist_for_each(ct, entry, &ct_cache[i], list)
{
if(ct->last_ct_timer != UDP_REPLY_TIMER_INIT)
ct->last_ct_timer = ct_timer;
}
}
}
else if (FFControlCmd.enable == 0){
ff_enable = 0;
}
else
return ERR_WRONG_COMMAND_PARAM;
return rc;
}
static U16 M_ipv4_cmdproc(U16 cmd_code, U16 cmd_len, U16 *pcmd)
{
U16 rc;
U16 querySize = 0;
U16 action;
switch (cmd_code)
{
case CMD_IPV4_CONNTRACK:
action = *pcmd;
rc = IPv4_HandleIP_CONNTRACK(pcmd, cmd_len);
if (rc == NO_ERR && (action == ACTION_QUERY || action == ACTION_QUERY_CONT))
querySize = sizeof(CtExCommand);
break;
case CMD_IP_ROUTE:
action = *pcmd;
rc = IP_HandleIP_ROUTE_RESOLVE(pcmd, cmd_len);
if (rc == NO_ERR && (action == ACTION_QUERY || action == ACTION_QUERY_CONT))
querySize = sizeof(RtCommand);
break;
case CMD_IPV4_RESET:
rc = IPv4_HandleIP_RESET();
break;
case CMD_IPV4_SET_TIMEOUT:
rc = IPv4_HandleIP_SET_TIMEOUT(pcmd, cmd_len);
break;
case CMD_IPV4_GET_TIMEOUT:
rc = IPv4_HandleIP_Get_Timeout(pcmd, cmd_len);
if (rc == NO_ERR)
querySize = sizeof(TimeoutCommand);
break;
/* IPv4 module is used to handle alternate configuration API */
case CMD_ALTCONF_SET:
rc = ALTCONF_HandleCONF_SET(pcmd, cmd_len);
break;
case CMD_ALTCONF_RESET:
rc = ALTCONF_HandleCONF_RESET_ALL(pcmd, cmd_len);
break;
case CMD_IPV4_FF_CONTROL:
rc = IPv4_HandleIP_FF_CONTROL(pcmd, cmd_len);
break;
case CMD_IPV4_SOCK_OPEN:
rc = SOCKET4_HandleIP_Socket_Open(pcmd, cmd_len);
break;
case CMD_IPV4_SOCK_CLOSE:
rc = SOCKET4_HandleIP_Socket_Close(pcmd, cmd_len);
break;
case CMD_IPV4_SOCK_UPDATE:
rc = SOCKET4_HandleIP_Socket_Update(pcmd, cmd_len);
break;
case CMD_IPV4_FRAGTIMEOUT:
case CMD_IPV4_SAM_FRAGTIMEOUT:
rc = IPv4_HandleIP_Set_FragTimeout(pcmd, cmd_len, (cmd_code == CMD_IPV4_SAM_FRAGTIMEOUT));
break;
#ifdef CFG_DIAGS
/* IPv4 module is used to handle fppDiag APIs */
case CMD_FPPDIAG_ENABLE:
rc = fppdiag_enable(pcmd, cmd_len);
break;
case CMD_FPPDIAG_DISABLE:
rc = fppdiag_disable();
break;
case CMD_FPPDIAG_UPDATE:
rc = fppdiag_update(pcmd, cmd_len);
break;
case CMD_FPPDIAG_DUMP_CTRS:
rc = fppdiag_dump_counters();
break;
#endif
default:
rc = ERR_UNKNOWN_COMMAND;
break;
}
*pcmd = rc;
return 2 + querySize;
}
#if !defined(COMCERTO_2000)
static void IPv4_Init_Fast_CTData(void)
{
int i;
U8 *p;
pFast_CTData_freelist = Fast_CTData;
for (i = 0, p = Fast_CTData; i < NUM_FAST_CTDATA - 1; i++, p += FAST_CTDATA_ENTRY_SIZE)
*(U8 **)p = p + FAST_CTDATA_ENTRY_SIZE;
*(U8 **)p = NULL;
}
#endif
int ipv4_init(void)
{
#if !defined(COMCERTO_2000)
/* Init Fast CTData pool */
IPv4_Init_Fast_CTData();
set_event_handler(EVENT_IPV4, M_ipv4_entry);
#else
struct pfe_ctrl *ctrl = &pfe->ctrl;
int i;
dlist_head_init(&ct_removal_list);
for (i = 0; i < NUM_CT_ENTRIES; i++)
{
struct hw_ct *ct = ctrl->hash_array_baseaddr + i * CLASS_ROUTE_SIZE;
ct->dma_addr = cpu_to_be32(ctrl->hash_array_phys_baseaddr + i * CLASS_ROUTE_SIZE);
dlist_head_init(&ct->list);
hw_ct_set_next(ct, 0);
hw_ct_set_flags(ct, 0);
}
#endif
set_cmd_handler(EVENT_IPV4, M_ipv4_cmdproc);
#if defined(COMCERTO_1000)
timer_init(&ipsec_ratelimiter_timer, AltConfIpsec_RateLimit_token_generator);
#endif
/* register ipv4 module to the timer service with 1 second granularity*/
timer_init(&ip_timer, M_ip_timer);
timer_add(&ip_timer, CT_TIMER_INTERVAL);
/* Set default values to programmable L4 timeouts */
udp_4o6_unidir_timeout = udp_unidir_timeout = UDP_UNIDIR_TIMEOUT * CT_TICKS_PER_SECOND;
udp_4o6_bidir_timeout = udp_bidir_timeout = UDP_BIDIR_TIMEOUT * CT_TICKS_PER_SECOND;
tcp_4o6_timeout = tcp_timeout = TCP_TIMEOUT * CT_TICKS_PER_SECOND;
other_4o6_proto_timeout= other_proto_timeout = OTHER_PROTO_TIMEOUT * CT_TICKS_PER_SECOND;
return 0;
}
void ipv4_exit(void)
{
#if defined(COMCERTO_2000)
struct pfe_ctrl *ctrl = &pfe->ctrl;
struct dlist_head *entry;
struct hw_ct *ct;
#endif
timer_del(&ip_timer);
/* Just call IPv4_HandleIP_RESET */
IPv4_HandleIP_RESET();
#if defined(COMCERTO_2000)
/* class pe's must be stopped by now, remove all pending entries */
dlist_for_each_safe(ct, entry, &ct_removal_list, rlist)
{
dlist_remove(&ct->rlist);
if (!IS_HASH_ARRAY(ctrl, ct))
{
dma_pool_free(ctrl->dma_pool, ct, be32_to_cpu(ct->dma_addr));
}
}
#endif
}