/*
 * IPv6 fastpath core function.
 * Author ql_xu
 *
 * ==FILEVERSION 20130316==
 * ============== modification revision ==============
 * 20130318  modify version to beta v0.01
 * 20130319  new API when CONFIG_NF_CONNTRACK_IPV6 closed, napt table is useless now.
 *
 */

#include "../net/bridge/br_private.h"
#include "ip6_fastpath_core.h"
#include <linux/if_pppox.h>
#include <linux/ipv6.h>
#include <net/dst.h>
#include <net/neighbour.h>
#include <net/ip6_route.h>


unsigned int br_ageing_time=270*HZ;

extern void neigh_hh_init(struct neighbour *n, struct dst_entry *dst);

inline int isNeighCreated(void *pDst, void *pSkb)
{
	struct dst_entry *dst = (struct dst_entry *)pDst;
	struct sk_buff *skb = (struct sk_buff *)pSkb;
	struct neighbour *neigh = dst_neigh_lookup_skb(dst, skb);
	int ret = 1;

	if (neigh == NULL) 
		return 0;

	if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE))) 
		ret = 0;
		
	neigh_release(neigh);
	return ret;
}

inline int needFragment(void *pSkb, void *pDst)
{
	struct dst_entry *dst = (struct dst_entry *)pDst;
	struct sk_buff *skb = (struct sk_buff *)pSkb;

	if(skb->len > dst->dev->mtu)
	{
		return 1;
	}

	return 0;
}

inline void ip6_fp_br_fdb_update(void *pSt)
{
	struct sk_buff *skb = (struct sk_buff *)pSt;
	struct net_bridge_port *br_port;

	br_port = br_port_get_rcu(skb->dev);
	if (br_port){
		br_fdb_update(br_port->br, br_port, eth_hdr(skb)->h_source, 0, false);
	}
}

inline void updateConxTimer(struct Ip6_Path_List_Entry *ptr)
{
	struct nf_conntrack_tuple_hash *h;
	struct nf_conn *ct;
	static unsigned int nf_ct_timeout;
	unsigned long newtime;

	/* look for tuple match */
	h = nf_conntrack_find_get(&init_net, NF_CT_DEFAULT_ZONE, &ptr->orig_tuple);
	if (!h) {
		//printk("lookup ct failed\n");
		return;
	}
	else {
		ct = nf_ct_tuplehash_to_ctrack(h);
	}

	if (ct->tuplehash[0].tuple.dst.protonum == IPPROTO_TCP) 
		nf_ct_timeout = ct->ct_net->ct.nf_ct_proto.tcp.timeouts[ct->proto.tcp.state];
	else
		nf_ct_timeout = ct->ct_net->ct.nf_ct_proto.udp.timeouts[UDP_CT_REPLIED];

	newtime = nf_ct_timeout + jiffies;

	if ( (newtime - ct->timeout.expires >= HZ)){
		mod_timer_pending(&ct->timeout, newtime);
	}

	nf_ct_put(ct);

}

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;
}

__IRAM int fp_ip6route_input(void *pSt  /*struct skbuff * */, 
								struct ipv6hdr *iph, struct in6_addr *fp_dip, 
								unsigned int course)
{
	struct sk_buff *skb = (struct sk_buff *)pSt;
	struct net_device *ndev=skb->dev;
	struct net_bridge_port *br_port;
	br_port = br_port_get_rcu(skb->dev);
	
	if (course == 1) {//upstream
		if(br_port!=NULL){
			if(br_port->br->dev == NULL)
				BUG();
			ndev = br_port->br->dev; 
		}
	}

	__ip6_route_input(skb, &iph->saddr, fp_dip, ndev);
	if (NULL == skb_dst(skb)){
		printk("can't find dest_entry for %s\n", ndev->name);
		return 0;
	}
	return 1;
}

#ifdef CONFIG_NF_CONNTRACK_IPV6
extern struct net init_net;

static enum LR_RESULT 
ip6_fastpath_nfconn2naptconn(struct sk_buff *skb, struct IP6_FP_NAPT_entry *napt, 
									struct nf_conntrack_tuple *orig_tuple,
									struct nf_conntrack_tuple *reply_tuple)
{
	struct nf_conntrack_tuple *tpdir1, *tpdir2;
#if 0	
	struct flowi6 fl6;
	struct dst_entry *dst1, *dst2;
#endif	
	int dst_type;
	//char ip6Str[INET6_ADDRSTRLEN];

	//exclude ::1
	if (ipv6_addr_loopback(&orig_tuple->src.u3.in6) || ipv6_addr_loopback(&orig_tuple->dst.u3.in6) ||
		ipv6_addr_loopback(&reply_tuple->src.u3.in6) || ipv6_addr_loopback(&reply_tuple->dst.u3.in6))
		goto FP_FAIL;

	/* ignore multicast and linklocal packet */
	dst_type = __ipv6_addr_type(&orig_tuple->dst.u3.in6);
	if (dst_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL))
		goto FP_FAIL;
	dst_type = __ipv6_addr_type(&reply_tuple->dst.u3.in6);
	if (dst_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL))
		goto FP_FAIL;
	
	napt->protocol = orig_tuple->dst.protonum;

#if USE_SKB_DEV
	if(skb)
	{
		struct net_bridge_port *br_port;

		if(skb->from_dev)
		{
			rcu_read_lock();
			br_port = br_port_get_rcu(skb->from_dev);
			rcu_read_unlock();
		}

		if(br_port)
		{
			tpdir1 = orig_tuple;
			tpdir2 = reply_tuple;
		}
		else if(skb->dev)
		{
			rcu_read_lock();
			br_port = br_port_get_rcu(skb->dev);
			rcu_read_unlock();
			if(br_port)
			{
				tpdir1 = reply_tuple;
				tpdir2 = orig_tuple;
			}
		}
		if(br_port==NULL)
		{
			printk("%s orig_tuple from %s, reply_tuple from %s go normal path\n", __func__, skb->from_dev->name, skb->dev->name);
			goto FP_FAIL;
		}
	}
	else
	{
#if 0
		memset(&fl6, 0, sizeof(struct flowi6));
		fl6.flowi6_proto = napt->protocol;
		fl6.daddr = orig_tuple->src.u3.in6;
		dst1 = ip6_route_output(&init_net, NULL, &fl6);
		fl6.daddr = reply_tuple->src.u3.in6;
		dst2 = ip6_route_output(&init_net, NULL, &fl6);
		if (dst1 && dst1->error==0 && (dst1->dev->priv_flags & IFF_EBRIDGE)) 
		{
			tpdir1 = orig_tuple;
			tpdir2 = reply_tuple;
		}else if (dst2 && dst2->error==0 && (dst2->dev->priv_flags & IFF_EBRIDGE)) {
			tpdir1 = reply_tuple;
			tpdir2 = orig_tuple;
		}
		else
		{
			//printk("%s get dst_entry for failed.\n", __func__);
			goto FP_FAIL;
		}
#else
		goto FP_FAIL;
#endif
	}
		napt->intIp = tpdir1->src.u3.in6;
		napt->intPort = ntohs(tpdir1->src.u.all);
		napt->extIp = tpdir2->dst.u3.in6;
		napt->extPort = ntohs(tpdir2->dst.u.all);
		napt->remIp = tpdir2->src.u3.in6;
		napt->remPort = ntohs(tpdir2->src.u.all);
	return LR_SUCCESS;

#else
	memset(&fl6, 0, sizeof(struct flowi6));
	fl6.flowi6_proto = napt->protocol;
	fl6.daddr = orig_tuple->src.u3.in6;

	dst1 = ip6_route_output(&init_net, NULL, &fl6);
	if (dst1->error) {
		//printk("%s get dst_entry for %s failed.\n", __func__, ip6_sprintf(ip6Str, &fl6.daddr));
		goto RELEASE_DST1;
	}
	fl6.daddr = reply_tuple->src.u3.in6;

	dst2 = ip6_route_output(&init_net, NULL, &fl6);
	if (dst2->error) {
		//printk("%s get dst_entry for %s failed.\n", __func__, ip6_sprintf(ip6Str, &fl6.daddr));
		goto RELEASE_DST2;
	}

	//if (dst1->dev->priv_flags & IFF_DOMAIN_ELAN)
	if (dst1->dev->priv_flags & IFF_EBRIDGE)
	{
		tpdir1 = orig_tuple;
		tpdir2 = reply_tuple;
	}
	//else if (dst2->dev->priv_flags & IFF_DOMAIN_ELAN)
	else if (dst2->dev->priv_flags & IFF_EBRIDGE)
	{
		tpdir1 = reply_tuple;
		tpdir2 = orig_tuple;
	}
	else
	{
		printk("%s orig_tuple from %s, reply_tuple from %s go normal path\n", __func__, dst1->dev->name, dst2->dev->name);
		goto RELEASE_DST2;
	}
	dst_release(dst1);
	dst_release(dst2);

	napt->intIp = tpdir1->src.u3.in6;
	napt->intPort = ntohs(tpdir1->src.u.all);
	napt->extIp = tpdir2->dst.u3.in6;
	napt->extPort = ntohs(tpdir2->dst.u.all);
	napt->remIp = tpdir2->src.u3.in6;
	napt->remPort = ntohs(tpdir2->src.u.all);

	return LR_SUCCESS;

RELEASE_DST2:
	dst_release(dst2);
RELEASE_DST1:
	dst_release(dst1);
#endif
FP_FAIL:
	return LR_FAILED;
}
/*
 * state: 0-unreplied    1-established
 */
#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
enum LR_RESULT fastpath_addRoutedIp6NaptConnection(struct sk_buff *skb, struct nf_conn * ct, 
											struct nf_conntrack_tuple *orig_tuple,
											struct nf_conntrack_tuple *reply_tuple,
											int state)
{
	struct IP6_FP_NAPT_entry napt;

	if (LR_SUCCESS != ip6_fastpath_nfconn2naptconn(skb, &napt, orig_tuple, reply_tuple))
		return LR_FAILED;
	
	return (ip6_fastpath_addNaptConnection(ct, &napt, state));
}

#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
enum LR_RESULT fastpath_updateIp6NaptConnection(struct sk_buff *skb, struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple, unsigned int mark)
{
	struct IP6_FP_NAPT_entry napt;

	if (LR_SUCCESS != ip6_fastpath_nfconn2naptconn(skb, &napt, orig_tuple, reply_tuple))
		return LR_FAILED;
	
	return (ip6_fastpath_updateNaptConnection(&napt, mark));
}

#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
enum LR_RESULT fastpath_delIp6NaptConnection (struct nf_conntrack_tuple *orig_tuple,
		struct nf_conntrack_tuple *reply_tuple)
{
	struct IP6_FP_NAPT_entry napt;

	if (LR_SUCCESS != ip6_fastpath_nfconn2naptconn(NULL, &napt, orig_tuple, reply_tuple))
		return LR_FAILED;
	
	return (ip6_fastpath_delNaptConnection(&napt));
}
#else//end of CONFIG_NF_CONNTRACK_IPV6
static enum LR_RESULT 
ip6_fastpath_parseNaptconn(struct IP6_FP_NAPT_entry *napt, int *course, struct ipv6hdr *ip6hdr)
{
	struct dst_entry *dst1, *dst2;
	int dst_type;
	struct flowi6 fl6;
	__u8 protoType=0;
	__u16 sport=0, dport=0;
	__u8 *l4hdr;
	char ip6Str[INET6_ADDRSTRLEN];

	//exclude ::1
	if (ipv6_addr_loopback(&ip6hdr->saddr) || ipv6_addr_loopback(&ip6hdr->daddr))
		goto FP_FAIL;

	/* ignore multicast and linklocal packet */
	dst_type = __ipv6_addr_type(&ip6hdr->daddr);
	if (dst_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL))
		goto FP_FAIL;

	Ip6_packetParser(ip6hdr, &protoType, &sport, &dport, &l4hdr);

	if (protoType == IPPROTO_ICMPV6)
		goto FP_FAIL;
	
	memset(&fl6, 0, sizeof(struct flowi6));
	fl6.flowi6_proto = protoType;
	fl6.daddr = ip6hdr->saddr;
	dst1 = ip6_route_output(&init_net, NULL, &fl6);
	if (dst1->error) {
		printk("%s get dst_entry for %s failed.\n", __func__, ip6_sprintf(ip6Str, &fl6.daddr));
		goto RELEASE_DST1;
	}
	fl6.daddr = ip6hdr->daddr;

	dst2 = ip6_route_output(&init_net, NULL, &fl6);
	if (dst2->error) {
		printk("%s get dst_entry for %s failed.\n", __func__, ip6_sprintf(ip6Str, &fl6.daddr));
		goto RELEASE_DST2;
	}

	napt->protocol = protoType;
	
	//if (dst1->dev->priv_flags & IFF_DOMAIN_ELAN)
	if (dst1->dev->priv_flags & IFF_EBRIDGE)
	{
		*course = 1;
		napt->intIp = napt->extIp = ip6hdr->saddr;
		napt->remIp = ip6hdr->daddr;
		napt->intPort = napt->extPort = sport;
		napt->remPort = dport;
	}
	//else if (dst2->dev->priv_flags & IFF_DOMAIN_ELAN)
	else if (dst2->dev->priv_flags & IFF_EBRIDGE)
	{
		*course = 2;
		napt->intIp = napt->extIp = ip6hdr->daddr;
		napt->remIp = ip6hdr->saddr;
		napt->intPort = napt->extPort = dport;
		napt->remPort = sport;
	}
	else
	{
		printk("%s orig_tuple from %s, reply_tuple from %s go normal path\n", __func__, dst1->dev->name, dst2->dev->name);
		goto RELEASE_DST2;
	}
	dst_release(dst1);
	dst_release(dst2);

	return LR_SUCCESS;

RELEASE_DST2:
	dst_release(dst2);
RELEASE_DST1:
	dst_release(dst1);
FP_FAIL:
	return LR_FAILED;
}
#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
enum LR_RESULT fastpath_addRoutedIp6NaptConnectionWithoutNFV6(struct ipv6hdr *ip6hdr)
{
	struct IP6_FP_NAPT_entry napt;
	int course;	//1-outbound   2-inbound

	if (LR_SUCCESS != ip6_fastpath_parseNaptconn(&napt, &course, ip6hdr))
		return LR_FAILED;
	
	return (ip6_fastpath_addNaptConnectionWithoutNFV6(&napt, course));
}

enum LR_RESULT fastpath_delIp6NaptConnectionWithoutNFV6(struct ipv6hdr *ip6hdr)
{
	struct IP6_FP_NAPT_entry napt;
	int course;	//1-outbound   2-inbound

	if (LR_SUCCESS != ip6_fastpath_parseNaptconn(&napt, &course, ip6hdr))
		return LR_FAILED;

	return (ip6_fastpath_delNaptConnectionWithoutNFV6(&napt, course));
}

#endif

__IRAM_SYS_MIDDLE int ip6_finish_output3(void *pskb, unsigned int course)
{
	struct sk_buff *skb = (struct sk_buff *)pskb;
	struct dst_entry *dst = skb_dst(skb);
	struct net_bridge_fdb_entry *fpdst;
	struct net_bridge *br;	//suppose skb->dev is bridge
	unsigned char *dest;
	struct neighbour *neigh =NULL, *neigh2 = NULL;
	struct hh_cache *hh = NULL;
	
	neigh = dst_neigh_lookup_skb(dst, skb);
	
	if (!neigh){
		printk("dst neighbour is NULL.dst %s \n", skb->dev->name);
		goto DROP;
	}	

	hh = (struct hh_cache *)(&neigh->hh);

	if(!hh)
	{
		if(dst->dev->header_ops && dst->dev->header_ops->cache!=NULL)
		{
			neigh_hh_init(neigh, dst);
			neigh2 = dst_neigh_lookup_skb(dst, skb);
			if (neigh2){
				hh = (struct hh_cache *)(&neigh2->hh);
				if (!hh)
					goto DROP1;
			} else 
				goto DROP1;
		}
		else {
			if(dev_hard_header(skb, dst->dev, ntohs(skb->protocol),
					neigh->ha, NULL, skb->len)<0)
				goto DROP1;    
		}
	}
	
	if(hh)
	{
		unsigned seq;
		int hh_len;
		
		do {
			int hh_alen;

			seq = read_seqbegin(&hh->hh_lock);
			hh_len = hh->hh_len;
			hh_alen = HH_DATA_ALIGN(hh_len);
			memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
		} while (read_seqretry(&hh->hh_lock, seq));

		skb_push(skb, hh_len);
		skb_set_mac_header(skb, 0);
	}
	
	//forwarding process	
	if (course == 1) {//upstream
		IP6_DEBUGP_PKT("%s xmit dev %s (%x)\n", __func__, skb->dev->name, (unsigned int)skb->dev->hard_start_xmit);
		skb->dev->netdev_ops->ndo_start_xmit(skb,skb->dev);
		goto SUCCEED;
	}
	else {
		if(unlikely(!(skb->dev->priv_flags & IFF_EBRIDGE))) {
			printk("[IPoE FastPath Exception] %s(%d): skb->dev is %s, course is %d; drops this packet!\n",
					__func__, __LINE__, skb->dev->name, course);
			goto DROP1;
		}
		
		dest = eth_hdr(skb)->h_dest;
		br = netdev_priv(skb->dev);
		if( !br) {
			printk("%s %d br is NULL.\n", __FUNCTION__, __LINE__);
			kfree_skb(skb);
			goto SUCCEED;
		}
  
		rcu_read_lock();
		if ((fpdst = __br_fdb_get(br, dest, 0)) != NULL)
		{
			rcu_read_unlock();
			if (netif_running(fpdst->dst->dev)) {
				skb->dev = fpdst->dst->dev;
				fpdst->dst->dev->netdev_ops->ndo_start_xmit(skb, fpdst->dst->dev);
			}
			else{
				kfree_skb(skb);
			}
			goto SUCCEED;
		}
		else{
			rcu_read_unlock();
			skb->dev->netdev_ops->ndo_start_xmit(skb,skb->dev);

			goto SUCCEED;
		}
	}

SUCCEED:
	neigh_release(neigh);
	if (neigh2)
		neigh_release(neigh2);
	return 1;
	
DROP1:
	neigh_release(neigh);
	if (neigh2)
		neigh_release(neigh2);
	
DROP:
	printk( "ip_finish_output3: No header cache and no neighbour, course=%d!\n", course);
	kfree_skb(skb);
	return -EINVAL;
}

#ifdef CONFIG_RTL867X_KERNEL_MIPS16_NET
__NOMIPS16
#endif
__IRAM_SYS_MIDDLE int Ip6_FastPath_Enter(struct sk_buff *skb)
{
	return (Ip6_FastPath_Process((void *)skb, ipv6_hdr(skb)));
}

static char digits_v6[] = "0123456789abcdef";

char *ip6_sprintf(char *ip6buf, const struct in6_addr *addr)
{
	int i;
	char *cp;
	const u_int16_t *a = (const u_int16_t *)addr;
	const u_int8_t *d;
	int dcolon = 0, zero = 0;

	cp = ip6buf;

	for (i = 0; i < 8; i++) {
		if (dcolon == 1) {
			if (*a == 0) {
				if (i == 7)
					*cp++ = ':';
				a++;
				continue;
			} else
				dcolon = 2;
		}
		if (*a == 0) {
			if (dcolon == 0 && *(a + 1) == 0) {
				if (i == 0)
					*cp++ = ':';
				*cp++ = ':';
				dcolon = 1;
			} else {
				*cp++ = '0';
				*cp++ = ':';
			}
			a++;
			continue;
		}
		d = (const u_char *)a;
		/* Try to eliminate leading zeros in printout like in :0001. */
		zero = 1;
		*cp = digits_v6[*d >> 4];
		if (*cp != '0') {
			zero = 0;
			cp++;
		}
		*cp = digits_v6[*d++ & 0xf];
		if (zero == 0 || (*cp != '0')) {
			zero = 0;
			cp++;
		}
		*cp = digits_v6[*d >> 4];
		if (zero == 0 || (*cp != '0')) {
			zero = 0;
			cp++;
		}
		*cp++ = digits_v6[*d & 0xf];
		*cp++ = ':';
		a++;
	}
	*--cp = '\0';
	return (ip6buf);
}