/*
 * IPv6 fastpath core function.
 * Author ql_xu
 *
 * ==FILEVERSION 20130316==
 * ============== modification revision ==============
 * 20130318  modify version to beta v0.01
 * 20130319  entry_path->conn maybe NULL, support the case when NF_CONNTRACK_IPV6 is closed.
 *
 */

#include <linux/fs.h>
#include <linux/seq_file.h>
#include "ip6_fastpath_core.h"


#define	MODULE_NAME			"Realtek SD5-FastPath"
#define	MODULE_VERSION_FP	"Realtek Ipv6FastPath-betaV0.01"

enum {
	ST_CONN_UNREP=0,
	ST_CONN_ESTABLISH,
	ST_PATH_EXIST
};

enum {
	FPTYPE_PUREROUTING=0,
	FPTYPE_SNAT=1,
	FPTYPE_SNPT=2,
	FPTYPE_DNAT=4,
	FPTYPE_DNPT=8,
	FPTYPE_SNAPT=0x3,
	FPTYPE_DNAPT=0xC
};
#ifndef CONFIG_REMOTE_ADSL_PHY
int ip6_fp_on=1;
#else
int ip6_fp_on=0;
#endif
#define PATH_ENTRY_AGEING_TIME	(90*HZ)	//1.5min

#define UDP_ELAPSE_TIME		(20*100)	//20s =>n tick
#define DEF_ELAPSE_TIME		(1*60*100)	//1min => n tick

#define	DEBUG_PROCFILE	/* Create ProcFile for debug */

extern unsigned long volatile jiffies;
#if !defined(CONFIG_NF_CONNTRACK_IPV6)
struct timer_list ip6_fp_timer;
#endif

/* ==================================================================================================== */

static rwlock_t ip6_fp_napt_lock;
static rwlock_t ip6_fp_path_lock;

struct Ip6_Napt_Table
{
	CTAILQ_HEAD(Ip6_Napt_list_entry_head, Ip6_Napt_List_Entry) list[IP6_NAPT_TABLE_LIST_MAX];
};

CTAILQ_HEAD(Ip6_Napt_list_inuse_head, Ip6_Napt_List_Entry) ip6_napt_list_inuse;
CTAILQ_HEAD(Ip6_Napt_list_free_head, Ip6_Napt_List_Entry) ip6_napt_list_free;

__DRAM
struct Ip6_Napt_Table *ip6_table_napt;

struct Ip6_Path_Table
{
	CTAILQ_HEAD(Ip6_Path_list_entry_head, Ip6_Path_List_Entry) list[IP6_PATH_TABLE_LIST_MAX];
};

CTAILQ_HEAD(Ip6_Path_list_inuse_head, Ip6_Path_List_Entry) ip6_path_list_inuse;
CTAILQ_HEAD(Ip6_Path_list_free_head, Ip6_Path_List_Entry) ip6_path_list_free;

__DRAM
struct Ip6_Path_Table *ip6_table_path;


static int isIpv6AlgConn(struct ipv6hdr *ip6hdr, __u16 sport, __u16 dport, __u8 protocol);

/* ==================================================================================================== */
static __u32 
Ip6_FastPath_Hash_NAPT_Entry(struct in6_addr *intIp,__u32 intPort,
							struct in6_addr *extIp, __u32 extPort,
							struct in6_addr *remIp, __u32 remPort)
{
	__u32 hash = 0;
	int i;

	for (i=0; i<8; i++)
	{
		hash ^= intIp->s6_addr16[i];
	}
	for (i=0; i<8; i++)
	{
		hash ^= extIp->s6_addr16[i];
	}
	for (i=0; i<8; i++)
	{
		hash ^= remIp->s6_addr16[i];
	}
	hash ^= intPort;
	hash ^= extPort;
	hash ^= remPort;
	
	return (IP6_NAPT_TABLE_LIST_MAX-1) & (hash ^ (hash >> 11));
}

inline static __u32
Ip6_FastPath_Hash_PATH_Entry(struct in6_addr *sip, __u32 sport, 
								struct in6_addr *dip, __u32 dport, 
								__u16 proto)
{
	register __u32 hash = 0;
	int i;
	
	for (i=0; i<8; i++)
	{
		hash ^= sip->s6_addr16[i];
	}
	for (i=0; i<8; i++)
	{
		hash ^= dip->s6_addr16[i];
	}
	hash ^= sport;
	hash ^= dport;
	hash ^= proto;

	return (IP6_PATH_TABLE_LIST_MAX-1) & (hash ^ (hash >> 11));
}


/* ==================================================================================================== */

enum LR_RESULT 
ip6_fastpath_addNaptConnection(void *ct, struct IP6_FP_NAPT_entry *napt, int state)
{
	struct Ip6_Napt_List_Entry *ep;
	struct Ip6_Napt_List_Entry *entry_napt;
	struct nf_conn *nf_ct;
#if IP6_FP_DBG
	char intipStr[INET6_ADDRSTRLEN], extipStr[INET6_ADDRSTRLEN], remipStr[INET6_ADDRSTRLEN];
#endif
	__u8 *proto;
	__u32 hash;

	if (isIpv6AlgConn(NULL, napt->intPort, napt->remPort, napt->protocol))
		return LR_FAILED;
	
	if( napt->protocol == IPPROTO_TCP ) {
		proto = (__u8 *)"TCP";
	}
	else if( napt->protocol == IPPROTO_UDP ) {
		proto = (__u8 *)"UDP";
	}
	else {
		proto = (__u8 *)"unknow";
	}

	hash = Ip6_FastPath_Hash_NAPT_Entry(&napt->intIp, napt->intPort, 
									&napt->extIp, napt->extPort, 
									&napt->remIp, napt->remPort);

#if 1
	IP6_DEBUGP_SYS("%s: P=%s int=%s/%d ext=%s/%d rem=%s/%d (H=%u, Ha=%u, Hb=%u),state=%x\n", __func__, 
			proto, 
			ip6_sprintf(intipStr, &napt->intIp), napt->intPort, 
			ip6_sprintf(extipStr, &napt->extIp), napt->extPort, 
			ip6_sprintf(remipStr, &napt->remIp), napt->remPort, hash, 
			Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol), 
			Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol), 
			state);
#endif
	/* Lookup */
	write_lock_bh(&ip6_fp_napt_lock);

	CTAILQ_FOREACH(ep, &ip6_table_napt->list[hash], napt_link) {
		if ((ep->protocol == napt->protocol) &&
			ipv6_addr_equal(&ep->intIp, &napt->intIp) &&
			(ep->intPort == napt->intPort) &&
			ipv6_addr_equal(&ep->extIp, &napt->extIp) &&
			(ep->extPort == napt->extPort) &&
			ipv6_addr_equal(&ep->remIp, &napt->remIp) &&
			(ep->remPort == napt->remPort))
		{
			IP6_DEBUGP_SYS("%s: ERROR - the entry already exist! \n", __func__);
			if (ep->state != 1)
			{
				if (state==1) {//current is the establish conntrack.
					if (ep->state == 2) {//PATH already exist.
						ep->state = 1;
						write_unlock_bh(&ip6_fp_napt_lock);
						return LR_SUCCESS;
					}
					else {
						ep->state = 1;
						entry_napt = ep;
						write_unlock_bh(&ip6_fp_napt_lock);
						goto ADD_PATH;
					}
				}
				else {
					if (ep->state == 2){
						write_unlock_bh(&ip6_fp_napt_lock);
						return LR_SUCCESS;
					}
					if ((++(ep->refcnt)) >= 10) {
						ep->state = 2;
						entry_napt = ep;
						write_unlock_bh(&ip6_fp_napt_lock);
						goto ADD_PATH;
					}
				}
			}

			write_unlock_bh(&ip6_fp_napt_lock);
			return LR_SUCCESS;
		}
	}

	if (state==0) {
		//printk("only add napt entry.\n");
		if(!CTAILQ_EMPTY(&ip6_napt_list_free) && !CTAILQ_EMPTY(&ip6_path_list_free)) {
			entry_napt = CTAILQ_FIRST(&ip6_napt_list_free);
			entry_napt->protocol = napt->protocol;
			entry_napt->intIp = napt->intIp;
			entry_napt->intPort = napt->intPort;
			entry_napt->extIp = napt->extIp;
			entry_napt->extPort = napt->extPort;
			entry_napt->remIp = napt->remIp;
			entry_napt->remPort = napt->remPort;
			entry_napt->state = 0;
			entry_napt->refcnt = 0;
			entry_napt->valid = 0xff;
			CTAILQ_REMOVE(&ip6_napt_list_free, entry_napt, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_napt_list_inuse, entry_napt, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_table_napt->list[hash], entry_napt, napt_link);
		}

		write_unlock_bh(&ip6_fp_napt_lock);
		return LR_SUCCESS;
	}
	
	if(!CTAILQ_EMPTY(&ip6_napt_list_free) && !CTAILQ_EMPTY(&ip6_path_list_free)) {
		entry_napt = CTAILQ_FIRST(&ip6_napt_list_free);
		entry_napt->protocol = napt->protocol;
		entry_napt->intIp = napt->intIp;
		entry_napt->intPort = napt->intPort;
		entry_napt->extIp = napt->extIp;
		entry_napt->extPort = napt->extPort;
		entry_napt->remIp = napt->remIp;
		entry_napt->remPort = napt->remPort;
		entry_napt->state = 1;
		entry_napt->valid = 0xff;
		CTAILQ_REMOVE(&ip6_napt_list_free, entry_napt, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_napt_list_inuse, entry_napt, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_napt->list[hash], entry_napt, napt_link);

		write_unlock_bh(&ip6_fp_napt_lock);
		goto ADD_PATH;
	}
	else {
		IP6_DEBUGP_SYS("%s: ERROR - Napt_list_free is empty! \n", __func__);
		write_unlock_bh(&ip6_fp_napt_lock);
		return LR_FAILED;
	}

	write_unlock_bh(&ip6_fp_napt_lock);
	
ADD_PATH:
	/* add Path Table Entry */
	if (1) {
		struct Ip6_Path_List_Entry *entry_path;

		if (CTAILQ_EMPTY(&ip6_path_list_free)) {
			entry_napt->state = 0;
			return LR_SUCCESS;
		}
		//upstream
		/* course = 1 (Outbound) */
		write_lock_bh(&ip6_fp_path_lock);
		
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol);
		entry_path = CTAILQ_FIRST(&ip6_path_list_free);
		entry_path->pps			= 0;
		entry_path->protocol	= &entry_napt->protocol;
		entry_path->in_sIp		= &entry_napt->intIp;
		entry_path->in_sPort	= &entry_napt->intPort;
		entry_path->in_dIp		= &entry_napt->remIp;
		entry_path->in_dPort	= &entry_napt->remPort;
		entry_path->out_sIp 	= &entry_napt->extIp;
		entry_path->out_sPort	= &entry_napt->extPort;
		entry_path->out_dIp 	= &entry_napt->remIp;
		entry_path->out_dPort	= &entry_napt->remPort;
		entry_path->out_ifname	= NULL;
		entry_path->course		= 1;
		entry_path->valid		= 0xff;
		entry_path->dst 		= NULL;
		entry_path->mark		= 0;
		entry_path->type		= 0;	/* Init: Normal (Only Routing) */
		entry_path->last_refresh_time		= jiffies;
		entry_path->fdb_ageing = jiffies;
		entry_path->conn		= ct;
		if (!ipv6_addr_equal(entry_path->in_sIp, entry_path->out_sIp)) {
			entry_path->type |= FPTYPE_SNAT;	/* SNAT */
		}
		if (*entry_path->in_sPort != *entry_path->out_sPort) {
			entry_path->type |= FPTYPE_SNPT;	/* SNPT */
		}
		nf_ct = (struct nf_conn *)ct;
		memcpy(&entry_path->orig_tuple, &nf_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(entry_path->orig_tuple));
		CTAILQ_REMOVE(&ip6_path_list_free, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_path_list_inuse, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_path->list[hash], entry_path, path_link);

		//downstream
		if (CTAILQ_EMPTY(&ip6_path_list_free)) {
			entry_path->valid = 0x00;
			CTAILQ_REMOVE(&ip6_table_path->list[hash], entry_path, path_link);
			CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
			entry_napt->state = 0;
			write_unlock_bh(&ip6_fp_path_lock);
			return LR_SUCCESS;
		}
		/* course = 2 (Inbound) */
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol);
		entry_path = CTAILQ_FIRST(&ip6_path_list_free);
		entry_path->pps			= 0;
		entry_path->protocol	= &entry_napt->protocol;
		entry_path->in_sIp		= &entry_napt->remIp;
		entry_path->in_sPort	= &entry_napt->remPort;
		entry_path->in_dIp		= &entry_napt->extIp;
		entry_path->in_dPort	= &entry_napt->extPort;
		entry_path->out_sIp 	= &entry_napt->remIp;
		entry_path->out_sPort	= &entry_napt->remPort;
		entry_path->out_dIp 	= &entry_napt->intIp;
		entry_path->out_dPort	= &entry_napt->intPort;
		entry_path->out_ifname	= NULL;
		entry_path->course		= 2;
		entry_path->valid		= 0xff;
		entry_path->dst 		= NULL;
		entry_path->type		= 0;	/* Init: Normal (Only Routing) */
		entry_path->mark		= 0;
		entry_path->last_refresh_time		= jiffies;
		entry_path->fdb_ageing = jiffies;
		entry_path->conn		= ct;
		if (!ipv6_addr_equal(entry_path->in_dIp, entry_path->out_dIp)) {
			entry_path->type |= FPTYPE_DNAT;	/* DNAT */
		}
		if (*entry_path->in_dPort != *entry_path->out_dPort) {
			entry_path->type |= FPTYPE_DNPT;	/* DNPT */
		}
		nf_ct = (struct nf_conn *)ct;
		memcpy(&entry_path->orig_tuple, &nf_ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(entry_path->orig_tuple));
		CTAILQ_REMOVE(&ip6_path_list_free, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_path_list_inuse, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_path->list[hash], entry_path, path_link);
		write_unlock_bh(&ip6_fp_path_lock);
		
	}
	
	return LR_SUCCESS;
}

enum LR_RESULT 
ip6_fastpath_delNaptConnection(struct IP6_FP_NAPT_entry *napt)
{
	struct Ip6_Napt_List_Entry *ep;
#if IP6_FP_DBG
	char intipStr[INET6_ADDRSTRLEN], extipStr[INET6_ADDRSTRLEN], remipStr[INET6_ADDRSTRLEN];
#endif
	__u8 *proto;
	__u32 hash1;

	if( napt->protocol == IPPROTO_TCP ) {
		proto = (__u8 *)"TCP";
	}
	else if( napt->protocol == IPPROTO_UDP ) {
		proto = (__u8 *)"UDP";
	}
	else {
		proto = (__u8 *)"unknow";
	}

	hash1 = Ip6_FastPath_Hash_NAPT_Entry(&napt->intIp, napt->intPort, 
									&napt->extIp, napt->extPort, 
									&napt->remIp, napt->remPort);

	IP6_DEBUGP_API("%s: P=%s int=%s/%u ext=%s/%u rem=%s:%u \n", __func__, 
			proto, 
			ip6_sprintf(intipStr, &napt->intIp), napt->intPort, 
			ip6_sprintf(extipStr, &napt->extIp), napt->extPort, 
			ip6_sprintf(remipStr, &napt->remIp), napt->remPort);

	/* Lookup */
	write_lock_bh(&ip6_fp_napt_lock);
	CTAILQ_FOREACH(ep, &ip6_table_napt->list[hash1], napt_link) {
		if ((ep->protocol == napt->protocol) &&
			ipv6_addr_equal(&ep->intIp, &napt->intIp) &&
			(ep->intPort == napt->intPort) &&
			ipv6_addr_equal(&ep->extIp, &napt->extIp) &&
			(ep->extPort == napt->extPort) &&
			ipv6_addr_equal(&ep->remIp, &napt->remIp) &&
			(ep->remPort == napt->remPort))
		{
			ep->valid = 0x00;
			CTAILQ_REMOVE(&ip6_table_napt->list[hash1], ep, napt_link);
			CTAILQ_REMOVE(&ip6_napt_list_inuse, ep, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_napt_list_free, ep, tqe_link);
			
			/* del Path Table Entry */
			if (1) {
				__u32	hash2;
				struct Ip6_Path_List_Entry *entry_path;
				
				/* course = 1 (Outbound) */
				write_lock_bh(&ip6_fp_path_lock);
				
				hash2 = Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol);
				CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash2], path_link) {
					if ((entry_path->protocol == &ep->protocol) && (entry_path->course == 1)) {
						entry_path->valid = 0x00;
						if( entry_path->dst ) {
							dst_release(entry_path->dst);							
						}
						CTAILQ_REMOVE(&ip6_table_path->list[hash2], entry_path, path_link);
						CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
						CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
						break;
					}
				}
				/* course = 2 (Inbound) */
				hash2 = Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol);
				CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash2], path_link) {
					if ((entry_path->protocol == &ep->protocol) && (entry_path->course == 2)) {
						entry_path->valid = 0x00;
						if( entry_path->dst ) {
							dst_release(entry_path->dst);							
						}
						CTAILQ_REMOVE(&ip6_table_path->list[hash2], entry_path, path_link);
						CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
						CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
						break;
					}
				}

				write_unlock_bh(&ip6_fp_path_lock);
			}

			write_unlock_bh(&ip6_fp_napt_lock);					
			return LR_SUCCESS;
		}
	}

	write_unlock_bh(&ip6_fp_napt_lock);	
	return LR_NONEXIST;
}

enum LR_RESULT
ip6_fastpath_updateNaptConnection(struct IP6_FP_NAPT_entry *napt, unsigned int mark)
{
	struct Ip6_Napt_List_Entry *ep;
#if IP6_FP_DBG
	char intipStr[INET6_ADDRSTRLEN], extipStr[INET6_ADDRSTRLEN], remipStr[INET6_ADDRSTRLEN];
#endif
	__u8 *proto;
	__u32 hash1;

	if( napt->protocol == IPPROTO_TCP ) {
		proto = (__u8 *)"TCP";
	}
	else if( napt->protocol == IPPROTO_UDP ) {
		proto = (__u8 *)"UDP";
	}
	else {
		proto = (__u8 *)"unknow";
	}

	hash1 = Ip6_FastPath_Hash_NAPT_Entry(&napt->intIp, napt->intPort, 
									&napt->extIp, napt->extPort, 
									&napt->remIp, napt->remPort);

	/* Lookup */
	read_lock_bh(&ip6_fp_napt_lock);
	CTAILQ_FOREACH(ep, &ip6_table_napt->list[hash1], napt_link) {
		if ((ep->protocol == napt->protocol) &&
			ipv6_addr_equal(&ep->intIp, &napt->intIp) &&
			(ep->intPort == napt->intPort) &&
			ipv6_addr_equal(&ep->extIp, &napt->extIp) &&
			(ep->extPort == napt->extPort) &&
			ipv6_addr_equal(&ep->remIp, &napt->remIp) &&
			(ep->remPort == napt->remPort))
		{
			IP6_DEBUGP_API("%s: P=%s int=%s/%u ext=%s/%u rem=%s/%u mark=%x\n", __func__, 
					proto,
					ip6_sprintf(intipStr, &napt->intIp), napt->intPort, 
					ip6_sprintf(extipStr, &napt->extIp), napt->extPort, 
					ip6_sprintf(remipStr, &napt->remIp), napt->remPort, mark);
			
			/* update Path Table Entry */
			if (1) {
				__u32	hash2;
				struct Ip6_Path_List_Entry *entry_path;
				
				/* course = 1 (Outbound) */
				hash2 = Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol);

				write_lock_bh(&ip6_fp_path_lock);
				CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash2], path_link) {
					if ((entry_path->protocol == &ep->protocol) && (entry_path->course == 1)) {
						entry_path->mark = mark;
						break;
					}
				}
				write_unlock_bh(&ip6_fp_path_lock);
#if 0//prepare for downstream IP QoS
				/* course = 2 (Inbound) */
				hash = Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol);
				CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link) {
					if ((entry_path->protocol == &ep->protocol) && (entry_path->course == 2)) {
						entry_path->mark = mark;
						break;
					}
				}
#endif
			}
			read_unlock_bh(&ip6_fp_napt_lock);					
			return LR_SUCCESS;
		}
	}

	read_unlock_bh(&ip6_fp_napt_lock);
	return LR_NONEXIST;
}

#if !defined(CONFIG_NF_CONNTRACK_IPV6)
static int reclaimOldFastpathConn(void)
{
	struct Ip6_Napt_List_Entry *napt_ep;
	__u32 hash;
	int path_exist=0;
	
	/* Lookup */
	write_lock_bh(&ip6_fp_napt_lock);
	CTAILQ_FOREACH(napt_ep, &ip6_napt_list_inuse, tqe_link)
	{
		/* del Path Table Entry */
		struct Ip6_Path_List_Entry *entry_path;
		
		/* course = 1 (Outbound) */
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt_ep->intIp, napt_ep->intPort, 
											&napt_ep->remIp, napt_ep->remPort, napt_ep->protocol);

		write_lock_bh(&ip6_fp_path_lock);
		CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link)
		{
			if ((entry_path->protocol == &napt_ep->protocol) && (entry_path->course == 1))
			{
				if ((jiffies - entry_path->last_refresh_time) >= PATH_ENTRY_AGEING_TIME)
				{
					entry_path->valid = 0x00;
					if( entry_path->dst ) {
						dst_release(entry_path->dst);							
					}
					CTAILQ_REMOVE(&ip6_table_path->list[hash], entry_path, path_link);
					CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
					CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
				}
				else
					path_exist = 1;
				
				break;
			}
		}
		/* course = 2 (Inbound) */
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt_ep->remIp, napt_ep->remPort, 
											&napt_ep->extIp, napt_ep->extPort, napt_ep->protocol);
		CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link)
		{
			if ((entry_path->protocol == &napt_ep->protocol) && (entry_path->course == 2))
			{
				if ((jiffies - entry_path->last_refresh_time) >= PATH_ENTRY_AGEING_TIME)
				{
					entry_path->valid = 0x00;
					if( entry_path->dst ) {
						dst_release(entry_path->dst);							
					}
					CTAILQ_REMOVE(&ip6_table_path->list[hash], entry_path, path_link);
					CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
					CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
				}
				else
					path_exist = 1;
				
				break;
			}
		}

		if (0 == path_exist)
		{
			napt_ep->valid = 0x00;
			hash = Ip6_FastPath_Hash_NAPT_Entry(&napt_ep->intIp, napt_ep->intPort, 
											&napt_ep->extIp, napt_ep->extPort, 
											&napt_ep->remIp, napt_ep->remPort);
			CTAILQ_REMOVE(&ip6_table_napt->list[hash], napt_ep, napt_link);
			CTAILQ_REMOVE(&ip6_napt_list_inuse, napt_ep, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_napt_list_free, napt_ep, tqe_link);
			
			break;
		}

		write_unlock_bh(&ip6_fp_path_lock);
	}

	write_unlock_bh(&ip6_fp_napt_lock);

	return 1;
}

static void ip6_fastpath_timeout(unsigned long data)
{
	reclaimOldFastpathConn();
	mod_timer(&ip6_fp_timer, jiffies + 60*HZ);
}

static void init_ip6_fastpath_time(void)
{
	init_timer(&ip6_fp_timer);
	ip6_fp_timer.function = ip6_fastpath_timeout;
	ip6_fp_timer.expires = jiffies + 60*HZ;
	add_timer(&ip6_fp_timer);
}

enum LR_RESULT 
ip6_fastpath_addNaptConnectionWithoutNFV6(struct IP6_FP_NAPT_entry *napt, int course)
{
	struct Ip6_Napt_List_Entry *ep;
	struct Ip6_Napt_List_Entry *entry_napt;
#if IP6_FP_DBG
	//char intipStr[INET6_ADDRSTRLEN], extipStr[INET6_ADDRSTRLEN], remipStr[INET6_ADDRSTRLEN];
#endif
	__u8 *proto;
	__u32 hash;

	if (isIpv6AlgConn(NULL, napt->intPort, napt->remPort, napt->protocol))
		return LR_FAILED;
	
	if( napt->protocol == IPPROTO_TCP ) {
		proto = (__u8 *)"TCP";
	}
	else if( napt->protocol == IPPROTO_UDP ) {
		proto = (__u8 *)"UDP";
	}
	else {
		proto = (__u8 *)"unknow";
	}

	hash = Ip6_FastPath_Hash_NAPT_Entry(&napt->intIp, napt->intPort, 
									&napt->extIp, napt->extPort, 
									&napt->remIp, napt->remPort);

#if 0
	IP6_DEBUGP_SYS("%s: P=%s int=%s/%d ext=%s/%d rem=%s/%d (H=%u, Ha=%u, Hb=%u),course=%x\n", __func__,
			proto, 
			ip6_sprintf(intipStr, &napt->intIp), napt->intPort, 
			ip6_sprintf(extipStr, &napt->extIp), napt->extPort, 
			ip6_sprintf(remipStr, &napt->remIp), napt->remPort, hash, 
			Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol), 
			Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol), 
			course);
#endif
	/* Lookup */
	write_lock_bh(&ip6_fp_napt_lock);

	CTAILQ_FOREACH(ep, &ip6_table_napt->list[hash], napt_link) {
		if ((ep->protocol == napt->protocol) &&
			ipv6_addr_equal(&ep->intIp, &napt->intIp) &&
			(ep->intPort == napt->intPort) &&
			ipv6_addr_equal(&ep->extIp, &napt->extIp) &&
			(ep->extPort == napt->extPort) &&
			ipv6_addr_equal(&ep->remIp, &napt->remIp) &&
			(ep->remPort == napt->remPort))
		{
			//IP6_DEBUGP_SYS("%s: ERROR - the entry already exist! \n", __func__);
			if (ep->state == ST_PATH_EXIST) {
				write_unlock_bh(&ip6_fp_napt_lock);
				return LR_SUCCESS;
			}
			else if (ep->state != ST_CONN_ESTABLISH)
			{
				if (ep->course != course) {//current is the establish conntrack.
					ep->state = ST_CONN_ESTABLISH;
					entry_napt = ep;
					write_unlock_bh(&ip6_fp_napt_lock);
					goto ADD_PATH;
				}
				else {
					if ((++(ep->refcnt)) >= 10) {
						ep->state = ST_PATH_EXIST;
						entry_napt = ep;
						write_unlock_bh(&ip6_fp_napt_lock);
						goto ADD_PATH;
					}
				}
			}

			write_unlock_bh(&ip6_fp_napt_lock);
			return LR_SUCCESS;
		}
	}

	//printk("only add napt entry.\n");
	if(!CTAILQ_EMPTY(&ip6_napt_list_free) && !CTAILQ_EMPTY(&ip6_path_list_free)) {
		entry_napt = CTAILQ_FIRST(&ip6_napt_list_free);
		entry_napt->protocol = napt->protocol;
		entry_napt->intIp = napt->intIp;
		entry_napt->intPort = napt->intPort;
		entry_napt->extIp = napt->extIp;
		entry_napt->extPort = napt->extPort;
		entry_napt->remIp = napt->remIp;
		entry_napt->remPort = napt->remPort;
		entry_napt->course = course;
		entry_napt->state = 0;
		entry_napt->refcnt = 0;
		entry_napt->valid = 0xff;
		CTAILQ_REMOVE(&ip6_napt_list_free, entry_napt, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_napt_list_inuse, entry_napt, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_napt->list[hash], entry_napt, napt_link);
	}
	else
		reclaimOldFastpathConn();

	write_unlock_bh(&ip6_fp_napt_lock);
	return LR_SUCCESS;

ADD_PATH:
	/* add Path Table Entry */
	if (1) {
		struct Ip6_Path_List_Entry *entry_path;

		if (CTAILQ_EMPTY(&ip6_path_list_free)) {
			entry_napt->state = 0;
			return LR_SUCCESS;
		}
		//upstream
		write_lock_bh(&ip6_fp_path_lock);
		
		/* course = 1 (Outbound) */
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt->intIp, napt->intPort, &napt->remIp, napt->remPort, napt->protocol);
		entry_path = CTAILQ_FIRST(&ip6_path_list_free);
		entry_path->protocol	= &entry_napt->protocol;
		entry_path->in_sIp		= &entry_napt->intIp;
		entry_path->in_sPort	= &entry_napt->intPort;
		entry_path->in_dIp		= &entry_napt->remIp;
		entry_path->in_dPort	= &entry_napt->remPort;
		entry_path->out_sIp 	= &entry_napt->extIp;
		entry_path->out_sPort	= &entry_napt->extPort;
		entry_path->out_dIp 	= &entry_napt->remIp;
		entry_path->out_dPort	= &entry_napt->remPort;
		entry_path->out_ifname	= NULL;
		entry_path->course		= 1;
		entry_path->valid		= 0xff;
		entry_path->dst 		= NULL;
		entry_path->mark		= 0;
		entry_path->type		= 0;	/* Init: Normal (Only Routing) */
		entry_path->last_refresh_time		= jiffies;
		entry_path->fdb_ageing = jiffies;
		entry_path->conn		= NULL;
		if (!ipv6_addr_equal(entry_path->in_sIp, entry_path->out_sIp)) {
			entry_path->type |= FPTYPE_SNAT;	/* SNAT */
		}
		if (*entry_path->in_sPort != *entry_path->out_sPort) {
			entry_path->type |= FPTYPE_SNPT;	/* SNPT */
		}
		CTAILQ_REMOVE(&ip6_path_list_free, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_path_list_inuse, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_path->list[hash], entry_path, path_link);

		//downstream
		if (CTAILQ_EMPTY(&ip6_path_list_free)) {
			entry_path->valid = 0x00;
			CTAILQ_REMOVE(&ip6_table_path->list[hash], entry_path, path_link);
			CTAILQ_REMOVE(&ip6_path_list_inuse, entry_path, tqe_link);
			CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
			entry_napt->state = 0;
			write_unlock_bh(&ip6_fp_path_lock);
			return LR_SUCCESS;
		}
		/* course = 2 (Inbound) */
		hash = Ip6_FastPath_Hash_PATH_Entry(&napt->remIp, napt->remPort, &napt->extIp, napt->extPort, napt->protocol);
		entry_path = CTAILQ_FIRST(&ip6_path_list_free);
		entry_path->protocol	= &entry_napt->protocol;
		entry_path->in_sIp		= &entry_napt->remIp;
		entry_path->in_sPort	= &entry_napt->remPort;
		entry_path->in_dIp		= &entry_napt->extIp;
		entry_path->in_dPort	= &entry_napt->extPort;
		entry_path->out_sIp 	= &entry_napt->remIp;
		entry_path->out_sPort	= &entry_napt->remPort;
		entry_path->out_dIp 	= &entry_napt->intIp;
		entry_path->out_dPort	= &entry_napt->intPort;
		entry_path->out_ifname	= NULL;
		entry_path->course		= 2;
		entry_path->valid		= 0xff;
		entry_path->dst 		= NULL;
		entry_path->type		= 0;	/* Init: Normal (Only Routing) */
		entry_path->mark		= 0;
		entry_path->last_refresh_time		= jiffies;
		entry_path->fdb_ageing = jiffies;
		entry_path->conn		= NULL;
		if (!ipv6_addr_equal(entry_path->in_dIp, entry_path->out_dIp)) {
			entry_path->type |= FPTYPE_DNAT;	/* DNAT */
		}
		if (*entry_path->in_dPort != *entry_path->out_dPort) {
			entry_path->type |= FPTYPE_DNPT;	/* DNPT */
		}
		CTAILQ_REMOVE(&ip6_path_list_free, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_path_list_inuse, entry_path, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_table_path->list[hash], entry_path, path_link);
		write_unlock_bh(&ip6_fp_path_lock);
		
	}
	
	return LR_SUCCESS;
}


enum LR_RESULT ip6_fastpath_delNaptConnectionWithoutNFV6(struct IP6_FP_NAPT_entry *napt, int course)
{
	return ip6_fastpath_delNaptConnection(napt);
}
#endif

/*delete concerning rules to filter chain*/
int Ip6_clearFastPathEntry(void)
{
	struct Ip6_Path_List_Entry *path_ep;
	struct Ip6_Napt_List_Entry *napt_ep;
	__u32	hash;

REMOVE_PATH:
	CTAILQ_FOREACH(path_ep, &ip6_path_list_inuse, tqe_link) {
		path_ep->valid = 0;
		if (path_ep->dst)
			dst_release(path_ep->dst);
		hash = Ip6_FastPath_Hash_PATH_Entry(path_ep->in_sIp, *path_ep->in_sPort, path_ep->in_dIp, *path_ep->in_dPort, *path_ep->protocol);
		CTAILQ_REMOVE(&ip6_table_path->list[hash], path_ep, path_link);
		CTAILQ_REMOVE(&ip6_path_list_inuse, path_ep, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_path_list_free, path_ep, tqe_link);
		goto REMOVE_PATH;
	}

REMOVE_NAPT:
	CTAILQ_FOREACH(napt_ep, &ip6_napt_list_inuse, tqe_link) {
		napt_ep->valid = 0;
		hash = Ip6_FastPath_Hash_NAPT_Entry(&napt_ep->intIp, napt_ep->intPort, &napt_ep->extIp, napt_ep->extPort, &napt_ep->remIp, napt_ep->remPort);
		CTAILQ_REMOVE(&ip6_table_napt->list[hash], napt_ep, napt_link);
		CTAILQ_REMOVE(&ip6_napt_list_inuse, napt_ep, tqe_link);
		CTAILQ_INSERT_TAIL(&ip6_napt_list_free, napt_ep, tqe_link);
		goto REMOVE_NAPT;
	}

	return 1;
}

/* ==================================================================================================== */
/* cached hardware header; allow for machine alignment needs.        */
#define HH_DATA_MOD     16
#define HH_DATA_ALIGN(__len) \
        (((__len)+(HH_DATA_MOD-1))&~(HH_DATA_MOD - 1))

#if 0//move to ip6_fastpath_api.c
static inline int 
Ip6_packetParser(struct ipv6hdr *ip6hdr, __u8 *protoType, 
					__u16 *sport, __u16 *dport, __u8 **l4hdr)
{
	__u8 *datap;
	int nxthdr;

	nxthdr = ip6hdr->nexthdr;
	datap = (__u8 *)(ip6hdr + 1);
	while (nxthdr != IPPROTO_NONE)
	{
		struct ipv6_opt_hdr *opt;
		*protoType = nxthdr;

		if ((nxthdr == IPPROTO_TCP) || (nxthdr == IPPROTO_UDP))
		{
			*l4hdr = datap;
			*sport = ntohs(*(__u16 *)datap);
			*dport = ntohs(*(__u16 *)(datap+2));
			break;
		}
		else if ((nxthdr == IPPROTO_ROUTING) || (nxthdr == IPPROTO_DSTOPTS)) {//routing header or target option header
			opt = (struct ipv6_opt_hdr *)datap;
			nxthdr = opt->nexthdr;
			datap += ((opt->hdrlen+1)<<3);
		}
		else if (nxthdr == IPPROTO_AH)
		{
			opt = (struct ipv6_opt_hdr *)datap;
			nxthdr = opt->nexthdr;
			datap += ((opt->hdrlen+2)<<2);
		}
#if 0
		/* any packet with hop-by-hop extension header can not pass fastpath, so ignore such packet */
		else if ((nxthdr == IPPROTO_HOPOPTS) || 
				(nxthdr == IPPROTO_ICMPV6) ||
				(nxthdr == IPPROTO_FRAGMENT) ||
				(nxthdr == IPPROTO_ESP)
				)
		{
			break;
		}
#endif
		else
			break;
	}

	return 1;
}
#endif

static int 
isIpv6AlgConn(struct ipv6hdr *ip6hdr, __u16 sport, __u16 dport, __u8 protocol)
{
	/****************	  ftp v6 alg**********************/
	if(protocol==IPPROTO_TCP && (sport==21 || dport==21))
		return 1;
	
	/* TODO: add more alg here */
	return 0;
}

int Ip6_FastPath_Process(void * pskb, struct ipv6hdr *ip6hdr)
{
	struct in6_addr *sip, *dip;
#if IP6_FP_DBG
	char sipStr[INET6_ADDRSTRLEN], dipStr[INET6_ADDRSTRLEN];
#endif
	__u8 protocol=0;
	__u16 sPort=0, dPort=0;
	__u8 *l4hdr=NULL;
#ifdef CONFIG_PPP
	int xmitOnPPP=0;
#endif
	void *dst;
	__u8 course;

	if (ip6hdr->nexthdr == IPPROTO_HOPOPTS)
		return 0;

	sip = &ip6hdr->saddr;
	dip = &ip6hdr->daddr;
	
	Ip6_packetParser(ip6hdr, &protocol, &sPort, &dPort, &l4hdr);
	if (isIpv6AlgConn(ip6hdr, sPort, dPort, protocol))
		return 0;

	write_lock_bh(&ip6_fp_path_lock);
		
	switch (protocol)
	{
		case IPPROTO_TCP: {
			struct tcphdr *tcph;
			__u32 hash;
			struct Ip6_Path_List_Entry *entry_path;
			
			tcph = (struct tcphdr*)l4hdr;
			//IP6_DEBUGP_PKT("==>> [%08X] SIP: %s/%u  -> DIP: %s/%u <TCP>\n", 
			//			tcph->check, 
			//			ip6_sprintf(sipStr, sip), sPort, 
			//			ip6_sprintf(dipStr, dip), dPort);
			if (tcph == NULL){
				write_unlock_bh(&ip6_fp_path_lock);
				return 0;
			}

			if (tcph->fin || tcph->rst || tcph->syn){
				write_unlock_bh(&ip6_fp_path_lock);
				return 0;
			}
			
			hash = Ip6_FastPath_Hash_PATH_Entry(sip, sPort, dip, dPort, protocol);
			CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link)
			{
				if ((*entry_path->in_sPort == sPort) && 
					(*entry_path->in_dPort == dPort) && 
					ipv6_addr_equal(entry_path->in_sIp, sip) &&
					ipv6_addr_equal(entry_path->in_dIp, dip) &&
					(*entry_path->protocol == IPPROTO_TCP))
				{
					if (entry_path->dst == NULL) {
						IP6_DEBUGP_PKT("course(%d) %s(%d)->%s(%d) entry_path->dst is NULL.\n", entry_path->course, 
									ip6_sprintf(sipStr, sip), sPort, ip6_sprintf(dipStr, dip), dPort);
						if (!fp_ip6route_input(pskb, ip6hdr, entry_path->out_dIp, entry_path->course)) {
							IP6_DEBUGP_PKT("course(%d) %s->%s can not find dst.\n", entry_path->course, ip6_sprintf(sipStr, sip), ip6_sprintf(dipStr, dip));
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						SetFPDst(pskb, &entry_path->dst);
						setSkbDst(pskb, NULL);
						// Check if downstream dst is bridge interface (ex. br0)
						if (entry_path->course==2 && !(((struct dst_entry *)entry_path->dst)->dev->priv_flags & IFF_EBRIDGE)) {
							// downstream dst not bridged-LAN, ignore this dst
							printk("WARNING!! incorrect dst, ignore it!\n");
							entry_path->dst = 0;
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						entry_path->out_ifname = fastpath_getdstifName(entry_path->dst);
					}
					
					if(needFragment(pskb, entry_path->dst))
					{
						write_unlock_bh(&ip6_fp_path_lock);
						return 0;
					}
#ifdef CONFIG_PPP
					if(ARPHRD_PPP == getDevTypeFromDestentry(entry_path->dst)){
						IP6_DEBUGP_PKT("pppoe proxy or upstream pppoe, interface = %s\n", entry_path->out_ifname);
						xmitOnPPP = 1;
					}
#endif
					
					if (isNeighCreated(entry_path->dst, pskb)
#ifdef CONFIG_PPP
							|| xmitOnPPP
#endif
							) {
						if ((entry_path->course == 1) && ((jiffies - entry_path->fdb_ageing) > br_ageing_time)) {
							entry_path->fdb_ageing = jiffies;
							ip6_fp_br_fdb_update(pskb);
						}

#ifdef CONFIG_PPP
						if (!xmitOnPPP) {
#endif
							setSkbDst(pskb, entry_path->dst);
							FastPathHoldDst(pskb);
							if (isDestLo(pskb)) goto FINISH;
#ifdef CONFIG_PPP
						}
#endif
						
						IP6_DEBUGP_PKT("FORWARD to [%s] \n", entry_path->out_ifname);
						switch(entry_path->type) {
							case FPTYPE_PUREROUTING: {	/* Only Routing */
								break;
							}
							case FPTYPE_SNAT: {	/* SNAT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAT(entry_path->out_sIp, sip, tcph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								break;
							}
							case FPTYPE_SNPT: /* SNPT */
							case FPTYPE_SNAPT: { /* SNAPT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT(entry_path->out_sIp, sip, *entry_path->out_sPort, tcph->source, tcph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								tcph->source = *entry_path->out_sPort;
								break;
							}
							case FPTYPE_DNAT: {	/* DNAT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAT(entry_path->out_dIp, dip, tcph->check);
								ip6hdr->daddr = *entry_path->out_dIp;
								break;
							}
							case FPTYPE_DNPT: /* DNPT */
							case FPTYPE_DNAPT: { /* DNAPT */ 					
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT(entry_path->out_dIp, dip, *entry_path->out_dPort, tcph->dest, tcph->check);
								ip6hdr->daddr = *entry_path->out_dIp;
								tcph->dest = *entry_path->out_dPort;
								break;
							}
							default: {
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT(entry_path->out_sIp, sip, *entry_path->out_sPort, tcph->source, tcph->check);
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT(entry_path->out_dIp, dip, *entry_path->out_dPort, tcph->dest, tcph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								tcph->source = *entry_path->out_sPort;
								ip6hdr->daddr = *entry_path->out_dIp;
								tcph->dest	= *entry_path->out_dPort;
								break;
							}
						}

						if ((jiffies - entry_path->last_refresh_time) > DEF_ELAPSE_TIME) {
							entry_path->last_refresh_time = jiffies;
						}
						dst = entry_path->dst;
						course = entry_path->course;
						entry_path->pps++;
						write_unlock_bh(&ip6_fp_path_lock);
#ifdef CONFIG_PPP
						if (xmitOnPPP)
							#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)||defined(CONFIG_ATP_SUPPORT_ETHUP)
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course, 0);
							#else
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course);
							#endif
						else {
#endif
							initSkbHdr(pskb);
							ip6_finish_output3(pskb, course);
#ifdef CONFIG_PPP
						}
#endif

						
						return NET_RX_DROP;
					}
					break;
				}
			}
			break;
		}
		case IPPROTO_UDP: {
			struct udphdr *udph;
			__u32 hash;
			struct Ip6_Path_List_Entry *entry_path;
			
			udph = (struct udphdr*)l4hdr;

			if (udph == NULL){
				write_unlock_bh(&ip6_fp_path_lock);
				return 0;
			}

			sPort = udph->source;
			dPort = udph->dest;
			
			//IP6_DEBUGP_PKT("==>> [%08X] SIP: %s/%u  -> DIP: %s/%u <UDP>\n", 
			//		udph->check, ip6_sprintf(sipStr, sip), udph->source, ip6_sprintf(dipStr, dip), udph->dest);
			
			hash = Ip6_FastPath_Hash_PATH_Entry(sip, sPort, dip, dPort, protocol);
			CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link) {
				if ((*entry_path->in_sPort == sPort) && 
					(*entry_path->in_dPort == dPort) && 
					ipv6_addr_equal(entry_path->in_sIp, sip) &&
					ipv6_addr_equal(entry_path->in_dIp, dip) &&
					(*entry_path->protocol == IPPROTO_UDP))
				{
					if (entry_path->dst == NULL) {
						if (!fp_ip6route_input(pskb, ip6hdr, entry_path->out_dIp, entry_path->course)) {
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						SetFPDst(pskb, &entry_path->dst);
						setSkbDst(pskb, NULL);
						// Check if downstream dst is bridge interface (ex. br0)
						if (entry_path->course==2 && !(((struct dst_entry *)entry_path->dst)->dev->priv_flags & IFF_EBRIDGE)) {
							// downstream dst not bridged-LAN, ignore this dst
							printk("WARNING!! incorrect dst, ignore it!\n");
							entry_path->dst = 0;
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						entry_path->out_ifname = fastpath_getdstifName(entry_path->dst);
					}

					if(needFragment(pskb, entry_path->dst))
					{
						write_unlock_bh(&ip6_fp_path_lock);
						return 0;
					}
#ifdef CONFIG_PPP
					if(ARPHRD_PPP == getDevTypeFromDestentry(entry_path->dst)){
						IP6_DEBUGP_PKT("pppoe proxy or upstream pppoe, interface = %s\n", entry_path->out_ifname);
						xmitOnPPP = 1;
					}
#endif
					
					if (isNeighCreated(entry_path->dst, pskb)
#ifdef CONFIG_PPP
							|| xmitOnPPP
#endif
							) {
						if ((entry_path->course == 1) && ((jiffies - entry_path->fdb_ageing) > br_ageing_time)) {
							entry_path->fdb_ageing = jiffies;
							ip6_fp_br_fdb_update(pskb);
						}
						
#ifdef CONFIG_PPP
						if (!xmitOnPPP) {
#endif
							setSkbDst(pskb, entry_path->dst);
							FastPathHoldDst(pskb);
							if (isDestLo(pskb)) goto FINISH;
#ifdef CONFIG_PPP
						}
#endif
						
						IP6_DEBUGP_PKT("FORWARD to [%s] \n", entry_path->out_ifname);
						switch(entry_path->type) {
							case FPTYPE_PUREROUTING: {	/* Only Routing */
								break;
							}
							case FPTYPE_SNAT: {	/* SNAT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAT_UDP(entry_path->out_sIp, sip, udph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								break;
							}
							case FPTYPE_SNPT: /* SNPT */
							case FPTYPE_SNAPT: {	/* SNAPT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT_UDP(entry_path->out_sIp, sip, *entry_path->out_sPort, udph->source, udph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								udph->source = *entry_path->out_sPort;
								break;
							}
							case FPTYPE_DNAT: {	/* DNAT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAT_UDP(entry_path->out_dIp, dip, udph->check);
								ip6hdr->daddr = *entry_path->out_dIp;
								break;
							}
							case FPTYPE_DNPT: /* DNPT */
							case FPTYPE_DNAPT: {	/* DNAPT */
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT_UDP(entry_path->out_dIp, dip, *entry_path->out_dPort, udph->dest, udph->check);
								ip6hdr->daddr = *entry_path->out_dIp;
								udph->dest = *entry_path->out_dPort;
								break;
							}
							default: {
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT_UDP(entry_path->out_sIp, sip, *entry_path->out_sPort, udph->source, udph->check);
								IP6_FASTPATH_ADJUST_CHKSUM_NAPT_UDP(entry_path->out_dIp, dip, *entry_path->out_dPort, udph->dest, udph->check);
								ip6hdr->saddr = *entry_path->out_sIp;
								ip6hdr->daddr = *entry_path->out_dIp;
								udph->source		= *entry_path->out_sPort;
								udph->dest			= *entry_path->out_dPort;
								break;
							}
						}
						if ( (jiffies - entry_path->last_refresh_time) > 20*HZ)
						{
							entry_path->last_refresh_time = jiffies;
							updateConxTimer(entry_path);
						}
						dst = entry_path->dst;
						course = entry_path->course;
						entry_path->pps++;
						write_unlock_bh(&ip6_fp_path_lock);
#ifdef CONFIG_PPP
						if (xmitOnPPP)
							#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)||defined(CONFIG_ATP_SUPPORT_ETHUP)
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course, 0);
							#else
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course);
							#endif
						else {
#endif
							initSkbHdr(pskb);
							ip6_finish_output3(pskb, course);
#ifdef CONFIG_PPP
						}
#endif						
						return NET_RX_DROP;
					}
					break;
				}
			}
			break;
		}
		case IPPROTO_ICMPV6:
		{
			break;	
		}
		default:
		{
			__u32 hash;
			struct Ip6_Path_List_Entry *entry_path;
			
			hash = Ip6_FastPath_Hash_PATH_Entry(sip, sPort, dip, dPort, protocol);
			CTAILQ_FOREACH(entry_path, &ip6_table_path->list[hash], path_link) {
				if ((*entry_path->protocol == protocol) && 
					ipv6_addr_equal(entry_path->in_sIp, sip) &&
					ipv6_addr_equal(entry_path->in_dIp, dip)) {
					if (entry_path->dst == NULL) {
						if (!fp_ip6route_input(pskb, ip6hdr, entry_path->out_dIp, entry_path->course)) {
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						SetFPDst(pskb, &entry_path->dst);
						setSkbDst(pskb, NULL);
						// Check if downstream dst is bridge interface (ex. br0)
						if (entry_path->course==2 && !(((struct dst_entry *)entry_path->dst)->dev->priv_flags & IFF_EBRIDGE)) {
							// downstream dst not bridged-LAN, ignore this dst
							printk("WARNING!! incorrect dst, ignore it!\n");
							entry_path->dst = 0;
							write_unlock_bh(&ip6_fp_path_lock);
							return 0;
						}
						entry_path->out_ifname = fastpath_getdstifName(entry_path->dst);
					}
					
					if(needFragment(pskb, entry_path->dst))
					{
						write_unlock_bh(&ip6_fp_path_lock);
						return 0;
					}
#ifdef CONFIG_PPP
					if(ARPHRD_PPP == getDevTypeFromDestentry(entry_path->dst)){
						IP6_DEBUGP_PKT("pppoe proxy or upstream pppoe, interface = %s\n", entry_path->out_ifname);
						xmitOnPPP = 1;
					}
#endif

					if (isNeighCreated(entry_path->dst, pskb)
#ifdef CONFIG_PPP
							|| xmitOnPPP
#endif
							) {
						if ((entry_path->course == 1) && ((jiffies - entry_path->fdb_ageing) > br_ageing_time)) {
							entry_path->fdb_ageing = jiffies;
							ip6_fp_br_fdb_update(pskb);
						}
						
#ifdef CONFIG_PPP
						if (!xmitOnPPP) {
#endif
							setSkbDst(pskb, entry_path->dst);
							FastPathHoldDst(pskb);
							if (isDestLo(pskb)) goto FINISH;
#ifdef CONFIG_PPP
						}
#endif
						
						IP6_DEBUGP_PKT("FORWARD to [%s] \n", entry_path->out_ifname);
						switch(entry_path->type) {
							case FPTYPE_PUREROUTING: /* Only Routing */
							case FPTYPE_SNPT: /* SNPT */
							case FPTYPE_DNPT:
								break;
							case FPTYPE_DNAT: {	/* DNAT */
								ip6hdr->daddr = *entry_path->out_dIp;
								break;
							}
							case FPTYPE_SNAT:{	/* SNAT */
								ip6hdr->saddr = *entry_path->out_sIp;

								break;
							}
							default: {
								ip6hdr->saddr = *entry_path->out_sIp;
								ip6hdr->daddr = *entry_path->out_dIp;
								break;
							}
						}
						
						//lifetime check, default lease time set to be 1 min
						if ((jiffies - entry_path->last_refresh_time) > DEF_ELAPSE_TIME) {
							entry_path->last_refresh_time = jiffies;
							updateConxTimer(entry_path);
						}
						dst = entry_path->dst;
						course = entry_path->course;
						entry_path->pps++;
						write_unlock_bh(&ip6_fp_path_lock);
#ifdef CONFIG_PPP
						if (xmitOnPPP)
							#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)||defined(CONFIG_ATP_SUPPORT_ETHUP)
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course, 0);
							#else
							pppoe_proxy_output(pskb, getDevFromDestentry(dst), course);
							#endif
						else {
#endif
							initSkbHdr(pskb);
							ip6_finish_output3(pskb, course);
#ifdef CONFIG_PPP
						}
#endif
						
						
						return NET_RX_DROP;
					}
					break;
				}
			}
			break;
		}
	}

FINISH:
	write_unlock_bh(&ip6_fp_path_lock);
	return 0;
}

#ifdef	DEBUG_PROCFILE
static int ip6_fastpath_table_napt_show(struct seq_file *m, void *v)
{
	struct Ip6_Napt_List_Entry *ep;
	char intipStr[INET6_ADDRSTRLEN], extipStr[INET6_ADDRSTRLEN], remipStr[INET6_ADDRSTRLEN];
	__u8 *proto;
	
	CTAILQ_FOREACH(ep, &ip6_napt_list_inuse, tqe_link) {
		if( ep->protocol  == IPPROTO_TCP ) {
			proto = (__u8 *)"TCP";
		}
		else if( ep->protocol  == IPPROTO_UDP ) {
			proto = (__u8 *)"UDP";
		}
		else {
			proto = (__u8 *)"unknow";
		}
		seq_printf(m, "~Napt: [%s] int=%s/%-5u\n             ext=%s/%-5u rem=%s/%-5u state=%d\n", 
				proto,
				ip6_sprintf(intipStr, &ep->intIp), ep->intPort, 
				ip6_sprintf(extipStr, &ep->extIp), ep->extPort, 
				ip6_sprintf(remipStr, &ep->remIp), ep->remPort,
				ep->state);
	}

	return 0;
}

static int ip6_fastpath_table_napt_open(struct inode *inode, struct file *file)
{
	return single_open(file, ip6_fastpath_table_napt_show, NULL);
}

static const struct file_operations ip6_fastpath_table_napt_proc_fops = {
	.open		= ip6_fastpath_table_napt_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
	.owner		= THIS_MODULE,
};

static int ip6_fastpath_table_path_show(struct seq_file *m, void *v)
{
	struct Ip6_Path_List_Entry *ep;
	char intsipStr[INET6_ADDRSTRLEN], indipStr[INET6_ADDRSTRLEN], outsipStr[INET6_ADDRSTRLEN], outdipStr[INET6_ADDRSTRLEN];
	__u8 *proto;
	
	CTAILQ_FOREACH(ep, &ip6_path_list_inuse, tqe_link) {
		if( *ep->protocol  == IPPROTO_TCP ) {
			proto = (__u8 *)"TCP";
		}
		else if( *ep->protocol  == IPPROTO_UDP ) {
			proto = (__u8 *)"UDP";
		}
		else if( *ep->protocol  == IPPROTO_GRE ) {
			proto = (__u8 *)"GRE";
		}
		else if( *ep->protocol  == IPPROTO_IPIP ) {
			proto = (__u8 *)"IPIP";
		}
		else {
			proto = (__u8 *)"unknow";
		}
		seq_printf(m, "~Path: <%5u> [%s] in-S=%s/%-5u in-D=%s/%-5u\n                     out-S=%s/%-5u out-D=%s/%-5u\n                     out-ifname=%-5s mark=%10x <%s> {%d} hash %u\n",
			ep->pps,
			proto,
			ip6_sprintf(intsipStr, ep->in_sIp), *ep->in_sPort, ip6_sprintf(indipStr, ep->in_dIp), *ep->in_dPort,
			ip6_sprintf(outsipStr, ep->out_sIp), *ep->out_sPort, ip6_sprintf(outdipStr, ep->out_dIp), *ep->out_dPort,
			ep->out_ifname, ep->mark, (ep->course==1)?"up":"down", ep->type,Ip6_FastPath_Hash_PATH_Entry(ep->in_sIp, *ep->in_sPort, ep->in_dIp, *ep->in_dPort, *ep->protocol));
	}

	return 0;
}

static int ip6_fastpath_table_path_open(struct inode *inode, struct file *file)
{
	return single_open(file, ip6_fastpath_table_path_show, NULL);
}

static const struct file_operations ip6_fastpath_table_path_proc_fops = {
	.open		= ip6_fastpath_table_path_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
	.owner		= THIS_MODULE,
};

static int ip6_fastpath_hash_path_show(struct seq_file *m, void *v)
{
	int i;
	
	for (i=0; i<IP6_PATH_TABLE_LIST_MAX; i++) {
		seq_printf(m, "%5d ", CTAILQ_TOTAL(&ip6_table_path->list[i]));
		if (i%12 == 11) seq_printf(m, "\n");
	}
	seq_printf(m, "\n");	

	return 0;
}

static int ip6_fastpath_hash_path_open(struct inode *inode, struct file *file)
{
	return single_open(file, ip6_fastpath_hash_path_show, NULL);
}

static const struct file_operations ip6_fastpath_hash_path_proc_fops = {
	.open		= ip6_fastpath_hash_path_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
	.owner		= THIS_MODULE,
};
#endif	/* DEBUG_PROCFILE */

static struct proc_dir_entry *IP6_FP_Proc_File;
#define PROCFS_NAME 		"Ip6FastPath"
#define REALTEK_IP6_FASTPATH_VERSION  MODULE_VERSION_FP       
#include <asm/uaccess.h>

static int ip6_fp_proc_read(struct seq_file*s , void* v)
{
	if(ip6_fp_on==1)
		seq_printf(s, "%s fastpath ON!\n",REALTEK_IP6_FASTPATH_VERSION);
	if(ip6_fp_on==0)
		seq_printf(s, "%s fastpath OFF!\n",REALTEK_IP6_FASTPATH_VERSION);
	
	return 0;
}

static int ip6_fp_proc_open(struct inode *inode, struct file *file)
{
	return(single_open(file, ip6_fp_proc_read, NULL));
}

#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
static ssize_t ip6_fp_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
	char proc_buffer[count];
	
	/* write data to the buffer */
	memset(proc_buffer, 0, sizeof(proc_buffer));
	if ( copy_from_user(proc_buffer, buffer, count) ) {
		return -EFAULT;
	}

	switch(proc_buffer[0]) {
		case '0':
			ip6_fp_on = 0;
			Ip6_clearFastPathEntry();
			break;
		case '1':
			ip6_fp_on = 1;
			break;
		default:
			printk("Error setting!\n");
	}

	return -1;
}

static const struct file_operations ip6_fp_proc_fops = {
	.open           = ip6_fp_proc_open,
	.write          = ip6_fp_proc_write,
	.read           = seq_read,
	.llseek         = seq_lseek,
	.release        = single_release,
};


static int ip6_fastpath_memory_init(void) 
{
	int i;
	
	/* Napt-Table Init */
	ip6_table_napt = (struct Ip6_Napt_Table *)kmalloc(sizeof(struct Ip6_Napt_Table), GFP_ATOMIC);
	if (ip6_table_napt == NULL) {
		IP6_DEBUGP_SYS("MALLOC Failed! (IPv6 Napt Table) \n");
		return -1;
	}
	CTAILQ_INIT(&ip6_napt_list_inuse);
	CTAILQ_INIT(&ip6_napt_list_free);
	for (i=0; i<IP6_NAPT_TABLE_LIST_MAX; i++) {
		CTAILQ_INIT(&ip6_table_napt->list[i]);
	}
	/* Napt-List Init */
	for (i=0; i<IP6_NAPT_TABLE_ENTRY_MAX; i++) {
		struct Ip6_Napt_List_Entry *entry_napt = (struct Ip6_Napt_List_Entry *)kmalloc(sizeof(struct Ip6_Napt_List_Entry), GFP_ATOMIC);
		if (entry_napt == NULL) {
			IP6_DEBUGP_SYS("MALLOC Failed! (IPv6 Napt Table Entry) \n");
			return -2;
		}
		CTAILQ_INSERT_TAIL(&ip6_napt_list_free, entry_napt, tqe_link);
	}
	
	/* Path-Table Init */
	ip6_table_path = (struct Ip6_Path_Table *)kmalloc(sizeof(struct Ip6_Path_Table), GFP_ATOMIC);
	if (ip6_table_path == NULL) {
		IP6_DEBUGP_SYS("MALLOC Failed! (IPv6 Path Table) \n");
		return -1;
	}
	CTAILQ_INIT(&ip6_path_list_inuse);
	CTAILQ_INIT(&ip6_path_list_free);
	for (i=0; i<IP6_PATH_TABLE_LIST_MAX; i++) {
		CTAILQ_INIT(&ip6_table_path->list[i]);
	}
	/* Path-List Init */
	for (i=0; i<IP6_PATH_TABLE_ENTRY_MAX; i++) {
		struct Ip6_Path_List_Entry *entry_path = (struct Ip6_Path_List_Entry *)kmalloc(sizeof(struct Ip6_Path_List_Entry), GFP_ATOMIC);
		if (entry_path == NULL) {
			IP6_DEBUGP_SYS("MALLOC Failed! (IPv6 Path Table Entry) \n");
			return -2;
		}
		CTAILQ_INSERT_TAIL(&ip6_path_list_free, entry_path, tqe_link);
	}

	rwlock_init(&ip6_fp_napt_lock);
	rwlock_init(&ip6_fp_path_lock);

	return 0;
}

static int __init ip6_fastpath_init(void)
{
#ifdef	DEBUG_PROCFILE
	/* proc file for debug */
	proc_create("fp6_napt", 0, init_net.proc_net, &ip6_fastpath_table_napt_proc_fops);
	proc_create("fp6_path", 0, init_net.proc_net, &ip6_fastpath_table_path_proc_fops);
	proc_create("fp6_hash_path", 0, init_net.proc_net, &ip6_fastpath_hash_path_proc_fops);
#endif	/* DEBUG_PROCFILE */
	
	printk("%s %s\n",MODULE_NAME, MODULE_VERSION_FP);

	//create proc
	IP6_FP_Proc_File= proc_create_data(PROCFS_NAME, 0644, NULL, &ip6_fp_proc_fops, NULL);

	if (IP6_FP_Proc_File == NULL) {
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
			PROCFS_NAME);
		return -ENOMEM;
	}

	printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME);
	
	ip6_fastpath_memory_init();

#if !defined(CONFIG_NF_CONNTRACK_IPV6)
	init_ip6_fastpath_time();
#endif
	
	return 0;
}

static void __exit ip6_fastpath_exit(void)
{
	printk("%s %s removed!\n", MODULE_NAME, MODULE_VERSION_FP);
}

module_init(ip6_fastpath_init);
module_exit(ip6_fastpath_exit);