/**
*	august_zhang created on 29th. Dec. 2011.
*	for testing RTL hooks
*
**/

//#define CONFIG_RTL_HOOK_CHAIN
#ifdef CONFIG_RTL_HOOK_CHAIN

#include <linux/netfilter_bridge/ebtables.h>
#include <linux/module.h>

//as we always arrange rtl_hook_chain.c in our driver directory.
#include "rtl.h"

//we have some bridge private struct like "net_bridge_port "
#include "../../../net/bridge/br_private.h"

#if defined (CONFIG_RTL_IGMP_SNOOPING)
#include <linux/etherdevice.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/igmp.h>
#include <net/checksum.h>
#include <net/rtl/rtl865x_igmpsnooping_glue.h>
#include <net/rtl/rtl865x_igmpsnooping.h>
#include <net/rtl/rtl865x_netif.h>
#include <net/rtl/rtl_nic.h>
extern unsigned int brIgmpModuleIndex;
extern unsigned int br0SwFwdPortMask;

#if defined (CONFIG_RTL_MLD_SNOOPING)
#include <linux/ipv6.h>
#include <linux/in6.h>
#include <linux/icmpv6.h>

#define IPV6_HEADER_LENGTH 40
#define MLDV1_REPORT 131
#define MLDV1_DONE 132
#define MLDV2_REPORT 143

extern int mldSnoopEnabled;
#endif
#endif//end of CONFIG_RTL_IGMP_SNOOPING


#ifdef CONFIG_RTL8672_BRIDGE_FASTPATH  	
#include "../brg_shortcut.h"  
#endif



#define CONFIG_RTL_HOOK_DBG
#ifdef CONFIG_RTL_HOOK_DBG
#define RTL_HOOK_DBG(fmt,args...)  printk("\033[1;36;40m<%s %d %s> \033[m"fmt, __FILE__, __LINE__, __func__, ##args)
#else
#define RTL_HOOK_DBG(fmt,args...)
#endif



enum
{
	HOOK_MAIN_SWITCH,
	HOOK_IGMP_SNOOPING = 1,
	HOOK_RTL_FASTBRG 	 = 2,

	
};
#define RTL_HOOK_PROC_DIR_NAME "rtl_hook"


#define RTL_HOOK_ON  1
#define RTL_HOOK_OFF 0

unsigned int rtl_hook_switch = 0;
#define CHECK_RTL_HOOK_ON_OFF(whichbit)  			if(RTL_HOOK_OFF == GET_RTL_HOOK_SWITCH(whichbit)) return NF_ACCEPT
#define GET_RTL_HOOK_SWITCH(whichbit)				(((rtl_hook_switch >> whichbit) & 0x1)?RTL_HOOK_ON : RTL_HOOK_OFF)

#define TURN_ON_RTL_HOOK_SWITCH(whichbit)			(rtl_hook_switch |= (0x1 << whichbit))
#define TURN_OFF_RTL_HOOK_SWITCH(whichbit)			(rtl_hook_switch &= (~(0x1 << whichbit)))

extern void ConvertMulticatIPtoMacAddr(__u32 group, unsigned char *gmac);
extern char igmp_type_check(struct sk_buff *skb, unsigned char *gmac,unsigned int *gIndex,unsigned int *moreFlag);
extern void br_update_igmp_snoop_fdb(unsigned char op, struct net_bridge *br, struct net_bridge_port *p, unsigned char *dest 
										   ,struct sk_buff *skb);
extern int ICMPv6_check(struct sk_buff *skb , unsigned char *gmac);

static unsigned int prehandle_igmp_report(unsigned int hook, struct sk_buff *skb,
				   const struct net_device *in,
				   const struct net_device *out,
				   int (*okfn)(struct sk_buff *))
{
	struct iphdr *iph;
	unsigned char proto;

	const unsigned char *dest = eth_hdr(skb)->h_dest;
	struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);

	struct net_bridge *br = p->br;

	char tmpOp;
	unsigned char operation;
	unsigned char macAddr[6];

	uint32 fwdPortMask = 0;

	CHECK_RTL_HOOK_ON_OFF(HOOK_MAIN_SWITCH);

	CHECK_RTL_HOOK_ON_OFF(HOOK_IGMP_SNOOPING);

	if ( !(br->dev->flags & IFF_PROMISC) && MULTICAST_MAC(dest) 
										  && (eth_hdr(skb)->h_proto == ETH_P_IP))
	{
		iph = (struct iphdr *)skb_network_header(skb);

		proto =  iph->protocol;  
		if (proto == IPPROTO_IGMP) 
		{	
			unsigned int gIndex=0;
			unsigned int moreFlag=1;

			struct net_bridge *br = p->br;
			
			while(moreFlag)
			{
				tmpOp=igmp_type_check(skb, macAddr, &gIndex, &moreFlag);
				if(tmpOp>0)
				{
					//printk("%s:%d,macAddr is 0x%x:%x:%x:%x:%x:%x\n",__FUNCTION__,__LINE__,macAddr[0],macAddr[1],macAddr[2],macAddr[3],macAddr[4],macAddr[5]);
					operation=(unsigned char)tmpOp;
					//operation :   2 - leave         1 - join
					br_update_igmp_snoop_fdb(operation, br, p, macAddr,skb);
				}
			}

			rtl_igmpMldProcess(brIgmpModuleIndex, (uint8_t*)(skb_mac_header(skb)), p->port_no, (uint32_t *)&fwdPortMask);
			RTL_HOOK_DBG("the pre fwdPortMask is 0x%x\n", fwdPortMask);
			//RTL_HOOK_DBG("Handle igmp packet. Add entry in module %d\n", brIgmpModuleIndex);
		}

	}


	if(!(br->dev->flags & IFF_PROMISC) && IPV6_MULTICAST_MAC(dest) 
										&& (eth_hdr(skb)->h_proto == ETH_P_IPV6))
	{

#if defined (CONFIG_RTL_MLD_SNOOPING) 	
		struct ipv6hdr *ipv6h=NULL;

#if defined (IPV6_MCAST_TO_UNICAST)
		tmpOp = ICMPv6_check(skb , macAddr);
		if(tmpOp > 0)
		{
			operation=(unsigned char)tmpOp;
#ifdef	DBG_ICMPv6
			if( operation == 1)
				printk("icmpv6 add from frame finish\n");
			else if(operation == 2)
				printk("icmpv6 del from frame finish\n");	
#endif
			br_update_igmp_snoop_fdb(operation, br, p, macAddr,skb);
		}
#endif
		if(mldSnoopEnabled)
		{
			ipv6h = (struct ipv6hdr *)skb_network_header(skb);
			proto = re865x_getIpv6TransportProtocol(ipv6h);
			/*icmp protocol*/
			if (proto == IPPROTO_ICMPV6) 
			{	
				rtl_igmpMldProcess(brIgmpModuleIndex, skb_mac_header(skb), p->port_no, &fwdPortMask);	
			}
		}
#endif
	}


	return NF_ACCEPT;
}

static unsigned int posthandle_igmp_and_multicast(unsigned int hook, struct sk_buff *skb,
				   const struct net_device *in,
				   const struct net_device *out,
				   int (*okfn)(struct sk_buff *))
{
	unsigned char proto;

	struct rtl_multicastDataInfo multicastDataInfo;
	struct rtl_multicastFwdInfo  multicastFwdInfo;

	int ret = FAILED;
	//unsigned char reserved = 0;

	const unsigned char *dest = eth_hdr(skb)->h_dest;
	struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
	struct net_bridge *br = p->br;

	CHECK_RTL_HOOK_ON_OFF(HOOK_MAIN_SWITCH);
	CHECK_RTL_HOOK_ON_OFF(HOOK_IGMP_SNOOPING);

	if ( !(br->dev->flags & IFF_PROMISC) && MULTICAST_MAC(dest) 
										  && (eth_hdr(skb)->h_proto == ETH_P_IP))
	{
		struct iphdr *iph;
		iph = (struct iphdr *)skb_network_header(skb);

		proto =  iph->protocol;  

		if(iph->daddr== 0xEFFFFFFA)
		{
			/*for microsoft upnp*/
			//reserved = 1;
			return NF_ACCEPT;
		}
	
		//august: handle the IGMP Packet, wo just allow the report packet flood to wan;
		if (proto == IPPROTO_IGMP) 
		{	
			struct igmphdr *igmp_hdr;
			int ihl;
			
			ihl = (iph->ihl) * 4;

			igmp_hdr = (struct igmphdr *)((char*)iph + ihl);
			
			if(igmp_hdr && igmp_hdr->type && 
									(igmp_hdr->type   == IGMP_HOST_MEMBERSHIP_REPORT 
									|| igmp_hdr->type == IGMPV2_HOST_MEMBERSHIP_REPORT
									|| igmp_hdr->type == IGMPV3_HOST_MEMBERSHIP_REPORT))
			{
				RTL_HOOK_DBG("igmp report packet last 3 mac is %x.%x.%x ! out is %s\n",skb->data[3], skb->data[4], skb->data[5], out->name);
				//only allow the report packet pass to wan
				if(out->priv_flags & IFF_DOMAIN_WAN)
					return NF_ACCEPT;
				else
					return NF_DROP;
			}
		}

		if((proto == IPPROTO_UDP) || (proto == IPPROTO_TCP))
		{
			multicastDataInfo.ipVersion=4;
			multicastDataInfo.sourceIp[0]=  (uint32)(iph->saddr);
			multicastDataInfo.groupAddr[0]=  (uint32)(iph->daddr);
				
			ret= rtl_getMulticastDataFwdInfo(brIgmpModuleIndex, &multicastDataInfo, &multicastFwdInfo);

			if(ret == SUCCESS)
			{
				if((1 << p->port_no) & multicastFwdInfo.fwdPortMask & br0SwFwdPortMask)
					return NF_ACCEPT;
				else
					return NF_DROP;
			}
			else
			{
				/*drop unknown multicast data*/
				if(multicastFwdInfo.unknownMCast)
					return NF_DROP;
			}
		}
	}

#if defined (CONFIG_RTL_MLD_SNOOPING)
	if(!(br->dev->flags & IFF_PROMISC) && IPV6_MULTICAST_MAC(dest) 
										&& (eth_hdr(skb)->h_proto == ETH_P_IPV6))
	{
		struct ipv6hdr *ipv6h = NULL;
		struct icmp6hdr *ptr = NULL;
		if(mldSnoopEnabled)
		{
			ipv6h  = (struct ipv6hdr *)skb_network_header(skb);
			proto =  re865x_getIpv6TransportProtocol(ipv6h);
			/*icmp protocol*/
			if (proto == IPPROTO_ICMPV6) 
			{
				ptr = (struct icmp6hdr* )((char*)ipv6h + IPV6_HEADER_LENGTH);

				if(ptr && ptr->icmp6_type && 
								(ptr->icmp6_type    == ICMPV6_MGM_REPORT 
								|| ptr->icmp6_type  == ICMPV6_MGM_REDUCTION
								|| ptr->icmp6_type  == MLDV2_REPORT))
				{
					if(out->priv_flags & IFF_DOMAIN_WAN)
						return NF_ACCEPT;
					else
						return NF_DROP;
				}	
			}
			else if ((proto ==IPPROTO_UDP) ||(proto ==IPPROTO_TCP))
			{
				multicastDataInfo.ipVersion=6;
				memcpy(&multicastDataInfo.sourceIp, &ipv6h->saddr, sizeof(struct in6_addr));
				memcpy(&multicastDataInfo.groupAddr, &ipv6h->daddr, sizeof(struct in6_addr));	
				ret = rtl_getMulticastDataFwdInfo(brIgmpModuleIndex, &multicastDataInfo, &multicastFwdInfo);
				if(ret == SUCCESS)
				{						
					if((1 << p->port_no) & multicastFwdInfo.fwdPortMask)
						return NF_ACCEPT;
					else
						return NF_DROP;
				}
			}
		}

	}
#endif
	return NF_ACCEPT;
}


static unsigned int handle_fastbrg_learn_destif(unsigned int hook, struct sk_buff *skb,
				   const struct net_device *in,
				   const struct net_device *out,
				   int (*okfn)(struct sk_buff *))
{
	CHECK_RTL_HOOK_ON_OFF(HOOK_MAIN_SWITCH);

	CHECK_RTL_HOOK_ON_OFF(HOOK_RTL_FASTBRG);

	skb_push(skb, ETH_HLEN);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,0)
	#ifdef CONFIG_RTK_FASTBRIDGE
	if(skb->protocol != ETH_P_ARP)
		brgScLearnDestItf(skb, skb->dev);
	#endif
#else
	#ifdef CONFIG_RTL8672_BRIDGE_FASTPATH
	if(skb->protocol != ETH_P_ARP)
		brgShortcutLearnDestItf(skb, skb->dev);
	#endif
#endif

	skb_pull(skb, ETH_HLEN);
	return NF_ACCEPT;
}


//Source Insight can not parse, I help it.
#define RTL_HOOK_CHAIN_OPS rtl_hook_chain_ops
static struct nf_hook_ops rtl_hook_chain_ops[] __read_mostly = {

	/*	august:
	*	The following two hooks is mainly used for checking igmpsnooping report packets; 
	*/
	{ .hook = prehandle_igmp_report,
	  .owner = THIS_MODULE,
	  .pf = PF_BRIDGE,
	  .hooknum = NF_BR_PRE_ROUTING,
	  .priority = NF_BR_PRI_NAT_DST_BRIDGED - 1, },

	{ .hook = posthandle_igmp_and_multicast,
	  .owner = THIS_MODULE,
	  .pf = PF_BRIDGE,
	  .hooknum = NF_BR_POST_ROUTING,
	  .priority = NF_BR_PRI_NAT_DST_BRIDGED - 1, },


	  
	/*	august:
	*	The following two hooks is mainly used for bridge fast path; 
	*   3ks for kevin's advice;
	*/
	{ .hook = handle_fastbrg_learn_destif,
	  .owner = THIS_MODULE,
	  .pf = PF_BRIDGE,
	  .hooknum = NF_BR_POST_ROUTING,
	  .priority = NF_BR_PRI_NAT_DST_BRIDGED - 1, },


#if 0
	/*
	*	this hook is another example for local in icmp packet,
	*	when kernel get an icmp packet, it clone it and pass it to the user space.
	*   Meanwhile , there is a corresponding daemon process which can get it by socket!
	*/
	{ .hook = rtl_handle_local_icmp,
	  .owner = THIS_MODULE,
	  .pf = PF_BRIDGE,
	  .hooknum = NF_BR_LOCAL_IN,
	  .priority = NF_BR_PRI_LAST, },
#endif
};



static int __init rtl_hook_chain_init(void)
{
	int ret;

	ret = nf_register_hooks(RTL_HOOK_CHAIN_OPS, ARRAY_SIZE(RTL_HOOK_CHAIN_OPS));
	if (ret < 0)
		return ret;

	RTL_HOOK_DBG("RTL hook chain registered\n");

	rtl_hook_proc_init();

	return 0;
}

static void __exit rtl_hook_chain_fini(void)
{
	nf_unregister_hooks(RTL_HOOK_CHAIN_OPS, ARRAY_SIZE(RTL_HOOK_CHAIN_OPS));
}





static int main_switch_proc_read(char *page, char **start, off_t off, 
		int count, int *eof, void *data)
{	  
	int len = 0;

	int isOn = GET_RTL_HOOK_SWITCH(HOOK_MAIN_SWITCH);
	
	len += sprintf(page + len, "the rtl hook main switch is %s\n", isOn?"ON":"OFF");
	
	len += sprintf(page + len, "Usage: \n");
	len += sprintf(page + len, "Turn on : echo 1 > /proc/rtl_hook/main_switch\n");
	len += sprintf(page + len, "Turn off: echo 0 > /proc/rtl_hook/main_switch\n");	

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;

	return len;
}



static int32 main_switch_proc_write( struct file *filp, const char *buff, unsigned long len, void *data )
{
	char 	tmpbuf[128];
	char	*strptr;	
	
	int32 	retval;
	
	if (buff && !copy_from_user(tmpbuf, buff, len))
	{
		tmpbuf[len] = '\0';
		
		strptr = tmpbuf;

		if(strlen(strptr)==0)
			goto errout;


		if(memcmp(strptr, "1", 1) == 0)
		{
			TURN_ON_RTL_HOOK_SWITCH(HOOK_MAIN_SWITCH);
			retval = SUCCESS;
		}
		else if(memcmp(strptr, "0", 1) == 0)
		{
			TURN_OFF_RTL_HOOK_SWITCH(HOOK_MAIN_SWITCH);
			retval = SUCCESS;
		}
		else
			retval = FAILED;

		if(retval == SUCCESS)
			printk("Write success ! \n");
		else
			printk("Write failed  ! \n");
		
	}
	else
	{
errout:
		printk("error input!\n");
		printk("Usage: \n");
		printk("Turn on : echo 1 > /proc/rtl_hook/main_switch\n");
		printk("Turn off: echo 0 > /proc/rtl_hook/main_switch\n");
	}

	return len;
}



static int igmp_proc_read(char *page, char **start, off_t off, 
		int count, int *eof, void *data)
{	  
	int len = 0;

	int isOn = GET_RTL_HOOK_SWITCH(HOOK_IGMP_SNOOPING);
	
	len += sprintf(page + len, "the rtl igmpsnooping is %s\n", isOn?"ON":"OFF");
	
	len += sprintf(page + len, "Usage: \n");
	len += sprintf(page + len, "Turn on : echo 1 > /proc/rtl_hook/igmpsnooping\n");
	len += sprintf(page + len, "Turn off: echo 0 > /proc/rtl_hook/igmpsnooping\n");	

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;

	return len;
}

static int32 igmp_proc_write( struct file *filp, const char *buff, unsigned long len, void *data )
{
	char 	tmpbuf[128];
	char	*strptr;	
	
	int32 	retval;
	
	if (buff && !copy_from_user(tmpbuf, buff, len))
	{
		tmpbuf[len] = '\0';
		
		strptr = tmpbuf;

		if(strlen(strptr)==0)
			goto errout;


		if(memcmp(strptr, "1", 1) == 0)
		{
			TURN_ON_RTL_HOOK_SWITCH(HOOK_IGMP_SNOOPING);
			retval = SUCCESS;
		}
		else if(memcmp(strptr, "0", 1) == 0)
		{
			TURN_OFF_RTL_HOOK_SWITCH(HOOK_IGMP_SNOOPING);
			retval = SUCCESS;
		}
		else
			retval = FAILED;

		if(retval == SUCCESS)
			printk("Write success ! \n");
		else
			printk("Write failed  ! \n");
		
	}
	else
	{
errout:
		printk("error input!\n");
		printk("Usage: \n");
		printk("Turn on : echo 1 > /proc/rtl_hook/igmpsnooping\n");
		printk("Turn off: echo 0 > /proc/rtl_hook/igmpsnooping\n");
	}

	return len;
}

static int fastbrg_proc_read(char *page, char **start, off_t off, 
		int count, int *eof, void *data)
{	  
	int len = 0;

	int isOn = GET_RTL_HOOK_SWITCH(HOOK_RTL_FASTBRG);
	
	len += sprintf(page + len, "the rtl fast bridge is %s\n", isOn?"ON":"OFF");
	
	len += sprintf(page + len, "Usage: \n");
	len += sprintf(page + len, "Turn on : echo 1 > /proc/rtl_hook/fast_bridge\n");
	len += sprintf(page + len, "Turn off: echo 0 > /proc/rtl_hook/fast_bridge\n");	

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;

	return len;
}

static int32 fastbrg_proc_write( struct file *filp, const char *buff, unsigned long len, void *data )
{
	char 	tmpbuf[128];
	char	*strptr;	
	
	int32 	retval;
	
	if (buff && !copy_from_user(tmpbuf, buff, len))
	{
		tmpbuf[len] = '\0';
		
		strptr = tmpbuf;

		if(strlen(strptr)==0)
			goto errout;


		if(memcmp(strptr, "1", 1) == 0)
		{
			TURN_ON_RTL_HOOK_SWITCH(HOOK_RTL_FASTBRG);
			retval = SUCCESS;
		}
		else if(memcmp(strptr, "0", 1) == 0)
		{
			TURN_OFF_RTL_HOOK_SWITCH(HOOK_RTL_FASTBRG);
			retval = SUCCESS;
		}
		else
			retval = FAILED;

		if(retval == SUCCESS)
			printk("Write success ! \n");
		else
			printk("Write failed  ! \n");
		
	}
	else
	{
errout:
		printk("error input!\n");
		printk("Usage: \n");
		printk("Turn on : echo 1 > /proc/rtl_hook/fast_bridge\n");
		printk("Turn off: echo 0 > /proc/rtl_hook/fast_bridge\n");
	}

	return len;
}


int rtl_hook_proc_init(void)
{
	int retval;

	struct proc_dir_entry *rtl_hook_proc_dir;

	struct proc_dir_entry *_hook_main_switch, *igmp_proc_entry, *fastbrg_proc_entry;
	
	if((rtl_hook_proc_dir = proc_mkdir(RTL_HOOK_PROC_DIR_NAME, NULL)) == NULL)
		return FAILED;

	_hook_main_switch = create_proc_entry("main_switch", 0, rtl_hook_proc_dir);
	if(_hook_main_switch != NULL)
	{
		_hook_main_switch->read_proc  = main_switch_proc_read;
		_hook_main_switch->write_proc = main_switch_proc_write;

		RTL_HOOK_DBG("sucessfully create proc entry for main switch\n");
		retval = SUCCESS;				
	}
	else
	{
		RTL_HOOK_DBG("can't create proc entry for main switch\n");
		retval = FAILED;
	}
	
	igmp_proc_entry = create_proc_entry("igmpsnooping", 0, rtl_hook_proc_dir);
	if(igmp_proc_entry != NULL)
	{
		igmp_proc_entry->read_proc  = igmp_proc_read;
		igmp_proc_entry->write_proc = igmp_proc_write;

		RTL_HOOK_DBG("sucessfully create proc entry for igmpsnooping\n");
		retval = SUCCESS;				
	}
	else
	{
		RTL_HOOK_DBG("can't create proc entry for igmpsnooping\n");
		retval = FAILED;
	}

	fastbrg_proc_entry = create_proc_entry("fast_bridge", 0, rtl_hook_proc_dir);
	if(igmp_proc_entry != NULL)
	{
		fastbrg_proc_entry->read_proc  = fastbrg_proc_read;
		fastbrg_proc_entry->write_proc = fastbrg_proc_write;

		RTL_HOOK_DBG("sucessfully create proc entry for fastbrg_proc_read\n");
		retval = SUCCESS;				
	}
	else
	{
		RTL_HOOK_DBG("can't create proc entry for fastbrg_proc_read\n");
		retval = FAILED;
	}

	return retval;				
}


module_init(rtl_hook_chain_init);
module_exit(rtl_hook_chain_fini);





#endif //end of CONFIG_RTL_HOOK_CHAIN