/*
 * Copyright (c) 2012, 2015, 2017-2018, The Linux Foundation. All rights reserved.
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all copies.
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#ifdef KVER32
#include <linux/kconfig.h>
#include <linux/version.h>
#include <generated/autoconf.h>
#else
#include <linux/autoconf.h>
#endif
#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include <linux/netfilter_arp.h>
#include <linux/inetdevice.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#ifdef KVER32
#include <linux/export.h>
#endif
#include <net/netfilter/nf_conntrack.h>
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
#include <linux/if_vlan.h>
#endif
#if defined (CONFIG_BRIDGE)
#include <../net/bridge/br_private.h>
#endif
#include <linux/ppp_defs.h>
#include <linux/filter.h>
#include <linux/if_pppox.h>
#include <linux/if_ppp.h>
#include <linux/ppp_channel.h>
#include <linux/ppp-comp.h>
#include <net/sock.h>
#include <net/route.h>
#include <net/netevent.h>
#include <net/ipv6.h>
#include <net/ip_fib.h>
#include "hsl_api.h"
#include "fal_nat.h"
#include "fal_ip.h"
#include "fal_fdb.h"
#include "hsl.h"
#include "nat_helper.h"
#include "napt_acl.h"
#include "lib/nat_helper_hsl.h"
#include "lib/nat_helper_dt.h"
#include "hsl_shared_api.h"
#include <net/addrconf.h>


#undef CONFIG_IPV6_HWACCEL


#ifdef CONFIG_IPV6_HWACCEL
#include <net/ndisc.h>
#include <net/neighbour.h>
#include <net/netevent.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/ipv6.h>
#include <linux/netfilter_ipv6.h>
#endif

//#define AP136_QCA_HEADER_EN 1
#define MAC_LEN 6
#define IP_LEN 4
#define ARP_HEADER_LEN 8

#define ARP_ENTRY_MAX 128

#define DESS_CHIP_VER	0x14
#define HOST_PREFIX_MAX	31

/* P6 is used by loop dev. */
#define S17_P6PAD_MODE_REG_VALUE 0x01000000

#define MULTIROUTE_WR

extern struct net init_net;

//char *nat_lan_dev_list = "eth0.1";
char nat_lan_dev_list[IFNAMSIZ*4] = "br-lan eth0.1";
char nat_wan_dev_list[IFNAMSIZ*5] = "eth0.2";

char nat_wan_port = 0x20;
int setup_wan_if = 0;
int setup_lan_if=0;

#define NAT_LAN_DEV_VID 1
#define NAT_WAN_DEV_VID 2

uint32_t nat_lan_vid  = NAT_LAN_DEV_VID;
uint32_t nat_wan_vid = NAT_WAN_DEV_VID;


static int wan_fid = 0xffff;
static fal_pppoe_session_t pppoetbl = {0};
static uint32_t pppoe_gwid = 0;
static char nat_bridge_dev[IFNAMSIZ*4] = "br-lan";
static uint8_t lanip[4] = {0}, lanipmask[4] = {0}, wanip[4] = {0};
#ifdef CONFIG_IPV6_HWACCEL
static struct in6_addr wan6ip = IN6ADDR_ANY_INIT;
static struct in6_addr lan6ip = IN6ADDR_ANY_INIT;
#endif

extern int nat_chip_ver;


#ifdef ISISC
struct ipv6_default_route_binding
{
    struct in6_addr next_hop;
    uint32_t nh_entry_id;
};
#endif

#ifdef MULTIROUTE_WR
#define MAX_HOST 8
struct wan_next_hop
{
    u_int32_t host_ip;
    u_int32_t entry_id;
    u_int32_t in_acl;
    u_int32_t in_use;
    u_int8_t  host_mac[6];
};
static struct net_device *multi_route_indev = NULL;
static struct wan_next_hop wan_nh_ent[MAX_HOST] = {{0}};

#define NAT_BACKGROUND_TASK

#ifdef NAT_BACKGROUND_TASK
#define NAT_HELPER_MSG_MAX 512

struct bg_ring_buf_cb {
	unsigned int read_idx;
	unsigned int write_idx;
	unsigned int num;
	unsigned int full_flag;
	struct nat_helper_bg_msg *buf;
};

struct bg_task_cb {
	/*struct semaphore bg_sem;  */   /*trigger thread work*/
	spinlock_t bg_lock;           /*ring buf access protect*/
	/*struct task_struct *bg_task;*/
	struct workqueue_struct *nat_wq;
	struct bg_ring_buf_cb ring;
};

enum{
	NAT_HELPER_ARP_ADD_MSG = 0,
	NAT_HELPER_ARP_DEL_MSG,
	NAT_HELPER_IPV6_MSG
};

struct arp_in_msg {
	uint8_t mac[ETH_ALEN];
	char name[IFNAMSIZ]; /* device name */
	uint32_t ip;
	struct net_device *in;
};

struct ipv6_msg {
	struct sk_buff *skb;
	struct net_device *in;
};


struct nat_helper_bg_msg {
	struct work_struct work;
	uint32_t msg_type;
	uint16_t sub_type;
	uint16_t reservd;
	union {
		struct arp_in_msg arp_in;
		struct ipv6_msg   ipv6;
	};
};


struct bg_task_cb task_cb;

int bg_ring_buf_write(struct nat_helper_bg_msg msg)
{
	unsigned int idx = 0;

	spin_lock_bh(&task_cb.bg_lock);
	
	if((task_cb.ring).full_flag &&
		((task_cb.ring).read_idx == (task_cb.ring).write_idx)) {
		HNAT_PRINTK("ring buf is full!\n");
		spin_unlock_bh(&task_cb.bg_lock);
		return -1;
	}
	msg.work = (task_cb.ring).buf[(task_cb.ring).write_idx].work;
	(task_cb.ring).buf[(task_cb.ring).write_idx] = msg;
	idx = (task_cb.ring).write_idx;

	(task_cb.ring).write_idx = ((task_cb.ring).write_idx+1)%NAT_HELPER_MSG_MAX;
	if(task_cb.ring.read_idx == task_cb.ring.write_idx)
		task_cb.ring.full_flag = 1;

	spin_unlock_bh(&task_cb.bg_lock);
	queue_work(task_cb.nat_wq, &(task_cb.ring).buf[idx].work);

	return 0;
}

int bg_ring_buf_read(struct nat_helper_bg_msg *msg)
{
	spin_lock_bh(&task_cb.bg_lock);
	task_cb.ring.read_idx = (task_cb.ring.read_idx+1)%NAT_HELPER_MSG_MAX;
	task_cb.ring.full_flag = 0;
	spin_unlock_bh(&task_cb.bg_lock);

	return 0;
}



#endif

a_uint32_t nat_helper_wan_port_get(void)
{
	a_uint32_t i = 0;

	for (i = 0; i < 6; i ++) {
		if ((nat_wan_port >> i) & 1)
			break;
	}

	return i;
}

static int wan_nh_get(u_int32_t host_ip)
{
    int i;

    host_ip = htonl(host_ip);

    for (i=0; i<MAX_HOST; i++)
    {
        if ((wan_nh_ent[i].host_ip != 0) && !memcmp(&wan_nh_ent[i].host_ip, &host_ip, 4))
        {
            // printk("%s %d\n", __FUNCTION__, __LINE__);
            // if ((wan_nh_ent[i].entry_id != 0) && (wan_nh_ent[i].in_acl != 1))
            if (wan_nh_ent[i].in_acl != 1)
            {
                wan_nh_ent[i].in_acl = 1;

                return i;
            }
            // printk("%s %d\n", __FUNCTION__, __LINE__);
        }
        printk("%s %d wan_nh_ent 0x%08x host_ip 0x%08x\n", __FUNCTION__, __LINE__, wan_nh_ent[i].host_ip, host_ip);
    }

    return -1;
}

static void wan_nh_add(u_int8_t *host_ip , u_int8_t *host_mac, u_int32_t id)
{
    int i;

    for( i = 0 ; i < MAX_HOST ; i++ )
    {
        if((wan_nh_ent[i].host_ip != 0) && !memcmp(&wan_nh_ent[i].host_ip, host_ip, 4))
        {
            if (host_mac == NULL) break;

            if(!memcmp(&wan_nh_ent[i].host_mac, host_mac,6))
                return;
            else
                break ;
        }

        if(wan_nh_ent[i].host_ip == 0)
            break;
    }

    if (i < MAX_HOST)
    {
        if ((wan_nh_ent[i].in_use) && (wan_nh_ent[i].in_acl)) return;

        memcpy(&wan_nh_ent[i].host_ip, host_ip, 4);
        if (host_mac != NULL)
        {
            memcpy(wan_nh_ent[i].host_mac, host_mac, 6);
            wan_nh_ent[i].entry_id = id;
            if ((wan_nh_ent[i].in_use) && !(wan_nh_ent[i].in_acl))
            {
                droute_add_acl_rules(*(uint32_t *)&lanip, *(uint32_t *)&lanipmask, id);
                /* set the in_acl flag */
                wan_nh_ent[i].in_acl = 1;
            }
        }
        else
        {
            /* set the in_use flag */
            wan_nh_ent[i].in_use = 1;
        }
        HNAT_PRINTK("%s: ip %08x (%d)\n" ,__func__, wan_nh_ent[i].host_ip, i);
    }
}

uint32_t get_next_hop(uint32_t daddr, uint32_t saddr)
{
    struct fib_result res;
#ifdef KVER32
    struct flowi4 fl =
    {
        .flowi4_iif =  multi_route_indev->ifindex,
        .flowi4_mark = 0,
        .flowi4_tos = 0,
        .flowi4_scope = RT_SCOPE_UNIVERSE,
        .daddr = htonl(daddr),
        .saddr = htonl(saddr),
    };
#else
    struct flowi fl = { .nl_u = { .ip4_u =
            {
                .daddr = daddr,
                .saddr = saddr,
                .tos = 0,
                .scope = RT_SCOPE_UNIVERSE,
            }
        },
        .mark = 0,
        .iif = multi_route_indev->ifindex
    };
#endif
    struct net    * net = dev_net(multi_route_indev);
    struct fib_nh *mrnh = NULL;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0))
    if (fib_lookup(net, &fl, &res) != 0)
#else
   if (fib_lookup(net, &fl, &res,0) != 0)
#endif
    {
        return 0;
    }
    else
    {
        mrnh = res.fi->fib_nh;
        if (NULL == mrnh)
        {
            return 0;
        }
    }

    return ntohl(mrnh->nh_gw);
}

uint32_t napt_set_default_route(fal_ip4_addr_t dst_addr, fal_ip4_addr_t src_addr)
{
    sw_error_t rv;

    /* search for the next hop (s) */
    if (!(get_aclrulemask() & (1 << S17_ACL_LIST_DROUTE)))
    {
        if (multi_route_indev && \
                (nf_athrs17_hnat_wan_type != NF_S17_WAN_TYPE_PPPOE) &&
                (nf_athrs17_hnat_wan_type != NF_S17_WAN_TYPE_PPPOES0))
        {
            uint32_t next_hop = get_next_hop(dst_addr, src_addr);

            HNAT_PRINTK("Next hop: %08x\n", next_hop);
            if (next_hop != 0)
            {
                fal_host_entry_t arp_entry;

                memset(&arp_entry, 0, sizeof(arp_entry));
                arp_entry.ip4_addr = next_hop;
                arp_entry.flags = FAL_IP_IP4_ADDR;
                rv = IP_HOST_GET(0, FAL_IP_ENTRY_IPADDR_EN, &arp_entry);
                if (rv != SW_OK)
                {
                    printk("%s: IP_HOST_GET error... (non-existed host: %08x?) \n", __func__, next_hop);
                    /* add into the nh_ent */
                    wan_nh_add((u_int8_t *)&next_hop, (u_int8_t *)NULL, 0);
                }
                else
                {
                    if (wan_nh_get(next_hop) != -1)
                        droute_add_acl_rules(*(uint32_t *)&lanip, *(uint32_t *)&lanipmask, arp_entry.entry_id);
                    else
                        printk("%s %d\n", __FUNCTION__, __LINE__);
                }
            }
            else
            {
                HNAT_PRINTK("no need to set the default route... \n");
                // set_aclrulemask (S17_ACL_LIST_DROUTE);
            }
        }
        else
        {
            printk("multi_route_indev %pK nf_athrs17_hnat_wan_type %d\n", multi_route_indev, nf_athrs17_hnat_wan_type);
        }
    }
    /* end next hop (s) */

    return SW_OK;
}
#endif /* MULTIROUTE_WR */

void qcaswitch_hostentry_flush(void)
{
    fal_host_entry_t hostentry;
    sw_error_t ret;

    do
    {
        memset(&hostentry, 0, sizeof(fal_host_entry_t));
        hostentry.entry_id = FAL_NEXT_ENTRY_FIRST_ID;
        ret = IP_HOST_NEXT (0, FAL_IP_ENTRY_ID_EN, &hostentry);
        if (SW_OK == ret)
        {
            IP_HOST_DEL(0, FAL_IP_ENTRY_IPADDR_EN, &hostentry);
        }
    }while (SW_OK == ret);

    return;
}

#ifdef CONFIG_IPV6_HWACCEL /* only for S17c */
static struct in6_addr* get_ipv6_default_gateway(void)
{
    /* ip_route_output_key can't return correct default nexhop
     * routes are less than 4 and it only searches in route
     * hash, not in fib, so use fib_lookup.
     */
    struct in6_addr *ip6addr = NULL;
    struct in6_addr des_addr = IN6ADDR_ANY_INIT;
    struct rt6_info *rt = rt6_lookup(&init_net, &des_addr, NULL, 0, 0);

    if (rt)
    {
        ip6addr = &rt->rt6i_gateway;
    }

    return ip6addr;
}

static int add_pppoev6_host_entry(void)
{
    struct in6_addr local_lan6ip = IN6ADDR_ANY_INIT;
    unsigned long  flags;
    int ppp_sid, ppp_sid2;
    unsigned char ppp_peer_mac[ETH_ALEN];
    unsigned char ppp_peer_mac2[ETH_ALEN];
    a_uint32_t ppp_peer_ip = 0;
    int wvid;
    fal_host_entry_t nh_arp_entry;
    sw_error_t rv;
    a_uint32_t droute_entry_id = 0;
    a_bool_t ena;
    static fal_pppoe_session_t pppoev6_sid_table = {0};
    struct  in6_addr *next_hop;

    local_irq_save(flags);
    memcpy(&local_lan6ip, &lan6ip, sizeof(struct in6_addr));
    ppp_sid2 = nf_athrs17_hnat_ppp_id2;
    ppp_sid = nf_athrs17_hnat_ppp_id;
    ppp_peer_ip = nf_athrs17_hnat_ppp_peer_ip;
    memcpy(ppp_peer_mac, nf_athrs17_hnat_ppp_peer_mac, ETH_ALEN);
    memcpy(ppp_peer_mac2, nf_athrs17_hnat_ppp_peer_mac2, ETH_ALEN);
    wvid = wan_fid;
    local_irq_restore(flags);

    if (NF_S17_WAN_TYPE_PPPOE != nf_athrs17_hnat_wan_type)
    {
        return SW_BAD_STATE;
    }

    if (__ipv6_addr_type(&local_lan6ip) == IPV6_ADDR_ANY)
    {
        /* Cannot get lanip6 successfully. */
        return SW_BAD_STATE;
    }
    if (0xffff == wvid)
    {
        printk("%s: Cannot get WAN vid!\n", __FUNCTION__);
        return SW_FAIL;
    }

    if (0 == nf_athrs17_hnat_ppp_peer_ip)
    {
        return SW_FAIL;
    }

    next_hop = get_ipv6_default_gateway();
    if (NULL == next_hop)
    {
        printk("No IPv6 Gateway!\n");
        return SW_BAD_STATE;
    }

    if (0 != ppp_sid)
    {
        if ((ppp_sid == ppp_sid2)||(0 == ppp_sid2)) /* v4 and v6 have the same session id */
        {
            memset(&nh_arp_entry, 0, sizeof(nh_arp_entry));
            nh_arp_entry.ip4_addr = ppp_peer_ip;
            nh_arp_entry.flags = FAL_IP_IP4_ADDR;
            rv = IP_HOST_GET(0, FAL_IP_ENTRY_IPADDR_EN, &nh_arp_entry);
            if (rv != SW_OK)
            {
                printk("%s: IP_HOST_GET error (0x%08x)\n", __func__, ppp_peer_ip);
                if (PPPOE_STATUS_GET(0, &ena) != SW_OK)
                {
                    if (!ena)
                    {
                        if (PPPOE_STATUS_SET(0, A_TRUE) != SW_OK)
                        {
                            aos_printk("Cannot enable the PPPoE mode\n");
                            return SW_FAIL;
                        }
                    }
                }
                pppoev6_sid_table.session_id = ppp_sid;
                pppoev6_sid_table.multi_session = 1;
                pppoev6_sid_table.uni_session = 1;
                pppoev6_sid_table.entry_id = 0;
                /* set the PPPoE edit reg (0x2200), and PPPoE session reg (0x5f000) */
                rv = PPPOE_SESSION_TABLE_ADD(0, &pppoev6_sid_table);
                if (rv == SW_OK)
                {
                    a_int32_t a_entry_id = -1;

                    PPPOE_SESSION_ID_SET(0, pppoev6_sid_table.entry_id, pppoev6_sid_table.session_id);
                    aos_printk("pppoe session: %d, entry_id: %d\n",
                               pppoev6_sid_table.session_id, pppoev6_sid_table.entry_id);
                    /* create the peer host ARP entry */
                    a_entry_id = arp_hw_add(S17_WAN_PORT, wan_fid, (void *)&ppp_peer_ip, (void *)ppp_peer_mac, 0);
                    if (a_entry_id >= 0) /* hostentry creation okay */
                    {
                        rv = IP_HOST_PPPOE_BIND(0, a_entry_id, pppoev6_sid_table.entry_id, A_TRUE);
                        if ( rv != SW_OK)
                        {
                            aos_printk("IP_HOST_PPPOE_BIND failed (entry: %d, rv: %d)... \n",
                                       a_entry_id, rv);
                            PPPOE_SESSION_TABLE_DEL(0, &pppoev6_sid_table);
                            return SW_FAIL;
                        }
                        droute_entry_id = a_entry_id;
                    }
                    else
                    {
                        PPPOE_SESSION_TABLE_DEL(0, &pppoev6_sid_table);
                        return SW_FAIL;
                    }
                }
                else
                {
                    aos_printk("PPPoE session add failed.. (id: %d)\n",
                               pppoev6_sid_table.session_id);
                    aos_printk("rv: %d\n", rv);
                    return SW_FAIL;
                }
            }
            else
            {
                droute_entry_id = nh_arp_entry.entry_id;
            }
            ipv6_droute_add_acl_rules(&local_lan6ip, droute_entry_id);
        }
        else /* Not the same session id */
        {
            if (PPPOE_STATUS_GET(0, &ena) != SW_OK)
            {
                if (!ena)
                {
                    if (PPPOE_STATUS_SET(0, A_TRUE) != SW_OK)
                    {
                        aos_printk("Cannot enable the PPPoE mode\n");
                        return SW_FAIL;
                    }
                }
            }
            memset(&nh_arp_entry, 0, sizeof(nh_arp_entry));
            memcpy((void *)&nh_arp_entry.ip6_addr, (void *)next_hop, sizeof(nh_arp_entry.ip6_addr));
            nh_arp_entry.flags = FAL_IP_IP6_ADDR;
            rv = IP_HOST_GET(0, FAL_IP_ENTRY_IPADDR_EN, &nh_arp_entry);
            if (rv != SW_OK)
            {
                /* ARP alread setup. */
                return SW_OK;
            }
            pppoev6_sid_table.session_id = ppp_sid2;
            pppoev6_sid_table.multi_session = 1;
            pppoev6_sid_table.uni_session = 1;
            pppoev6_sid_table.entry_id = 0;
            /* set the PPPoE edit reg (0x2200), and PPPoE session reg (0x5f000) */
            rv = PPPOE_SESSION_TABLE_ADD(0, &pppoev6_sid_table);
            if (rv == SW_OK)
            {
                a_int32_t a_entry_id = -1;

                PPPOE_SESSION_ID_SET(0, pppoev6_sid_table.entry_id, pppoev6_sid_table.session_id);
                aos_printk("pppoe session: %d, entry_id: %d\n",
                           pppoev6_sid_table.session_id, pppoev6_sid_table.entry_id);
                /* create the peer host ARP entry */
                a_entry_id = arp_hw_add(S17_WAN_PORT, wan_fid, (void *)next_hop, ppp_peer_mac2, 1);
                if (a_entry_id >= 0) /* hostentry creation okay */
                {
                    rv = IP_HOST_PPPOE_BIND(0, a_entry_id, pppoev6_sid_table.entry_id, A_TRUE);
                    if ( rv != SW_OK)
                    {
                        aos_printk("IP_HOST_PPPOE_BIND failed (entry: %d, rv: %d)... \n",
                                   a_entry_id, rv);
                        PPPOE_SESSION_TABLE_DEL(0, &pppoev6_sid_table);
                        return SW_FAIL;
                    }
                    droute_entry_id = a_entry_id;
                }
                else
                {
                    PPPOE_SESSION_TABLE_DEL(0, &pppoev6_sid_table);
                    return SW_FAIL;
                }
            }
            else
            {
                aos_printk("PPPoE session add failed.. (id: %d)\n",
                           pppoev6_sid_table.session_id);
                aos_printk("rv: %d\n", rv);
                return SW_FAIL;
            }
            ipv6_droute_add_acl_rules(&local_lan6ip, droute_entry_id);
        }
    }

    return SW_OK;
}

uint32_t napt_set_ipv6_default_route(void)
{
    sw_error_t rv;
    static a_bool_t ipv6_droute_setup = A_FALSE;
    static struct ipv6_default_route_binding ipv6_droute_bind = {IN6ADDR_ANY_INIT,0};
    struct in6_addr local_lan6ip = IN6ADDR_ANY_INIT;
    unsigned long  flags;

	if (((nat_chip_ver&0xffff)>>8) == DESS_CHIP_VER)
		return SW_OK;

    /* search for the next hop (s)*/
    if (NF_S17_WAN_TYPE_IP == nf_athrs17_hnat_wan_type)
    {
        struct  in6_addr *next_hop = get_ipv6_default_gateway();

        // printk("IPv6 next hop: %pI6\n", next_hop);

        if (next_hop != NULL)
        {
            fal_host_entry_t ipv6_neigh_entry;

            if (__ipv6_addr_type(next_hop) == IPV6_ADDR_LINKLOCAL)
                return SW_OK;

            local_irq_save(flags);
            memcpy(&local_lan6ip, &lan6ip, sizeof(struct in6_addr));
            local_irq_restore(flags);

            memset(&ipv6_neigh_entry, 0, sizeof(ipv6_neigh_entry));
            memcpy(&ipv6_neigh_entry.ip6_addr, next_hop, 16);
            ipv6_neigh_entry.flags = FAL_IP_IP6_ADDR;
            rv = IP_HOST_GET(0, FAL_IP_ENTRY_IPADDR_EN, &ipv6_neigh_entry);
            if ((rv != SW_OK)||(__ipv6_addr_type(&local_lan6ip) == IPV6_ADDR_ANY))
            {
                if (ipv6_droute_setup)
                {
                    ipv6_droute_del_acl_rules();
                    memset(&ipv6_droute_bind, 0, sizeof(ipv6_droute_bind));
                    ipv6_droute_setup = A_FALSE;
                }
            }
            else
            {
                if (ipv6_droute_setup)
                {
                    if (!ipv6_addr_equal(&ipv6_droute_bind.next_hop, next_hop) ||
                            ipv6_droute_bind.nh_entry_id != ipv6_neigh_entry.entry_id)
                    {
                        ipv6_droute_del_acl_rules();
                    }
                }
                ipv6_droute_bind.next_hop = *next_hop;
                ipv6_droute_bind.nh_entry_id = ipv6_neigh_entry.entry_id;

                ipv6_droute_add_acl_rules(&local_lan6ip, ipv6_neigh_entry.entry_id);
                ipv6_droute_setup = A_TRUE;
            }
        }
        else
        {
            if (ipv6_droute_setup)
            {
                ipv6_droute_del_acl_rules();
                memset(&ipv6_droute_bind, 0, sizeof(ipv6_droute_bind));
                ipv6_droute_setup = A_FALSE;
            }
        }
    }
    else if (NF_S17_WAN_TYPE_IP == nf_athrs17_hnat_wan_type)
    {
        add_pppoev6_host_entry();
    }

    return SW_OK;
}
#endif /* ifdef CONFIG_IPV6_HWACCEL */

static sw_error_t hnat_add_host_route(
		fal_ip4_addr_t ip_addr,
		uint32_t prefix_len)
{
	fal_host_route_t host_route;

	memset(&host_route, 0, sizeof(fal_host_route_t));

	IP_HOST_ROUTE_GET(0, 0, &host_route);
	if ((host_route.route_addr.ip4_addr == ip_addr) &&
	    (host_route.prefix_length == prefix_len) &&
	    host_route.valid)
	    return SW_OK;

	host_route.valid = A_TRUE;
	host_route.vrf_id = 0;
	host_route.ip_version = 0;
	host_route.route_addr.ip4_addr = ip_addr;
	host_route.prefix_length = prefix_len;

	return IP_HOST_ROUTE_ADD(0, 0, &host_route);
}
static sw_error_t setup_interface_entry(char *list_if, int is_wan)
{
    char temp[IFNAMSIZ*4]; /* Max 4 interface entries right now. */
    char *dev_name, *list_all;
    struct net_device *nat_dev;
    struct in_device *in_device_lan = NULL, *in_device_wan = NULL;
    uint8_t *devmac, if_mac_addr[MAC_LEN];
    char *br_name;
    uint32_t vid = 0;
    sw_error_t setup_error;
    uint32_t ipv6 = 0;

    memcpy(temp, list_if, strlen(list_if)+1);
    list_all = temp;

    setup_error = SW_FAIL;
    rcu_read_lock();
    while ((dev_name = strsep(&list_all, " ")) != NULL)
    {
        nat_dev = dev_get_by_name(&init_net, dev_name);
        if (NULL == nat_dev)
        {
            // printk("%s: Cannot get device %s by name!\n", __FUNCTION__, dev_name);
            setup_error = SW_FAIL;
            continue;
        }
#if defined (CONFIG_BRIDGE)
#ifdef KVER32
        if (NULL != br_port_get_rcu(nat_dev)) /* under bridge interface. */
        {
            /* Get bridge interface name */
            br_name = (char *)(br_port_get_rcu(nat_dev)->br->dev->name);
            if (!is_wan) {
                strlcat (nat_lan_dev_list, " ", sizeof(nat_lan_dev_list));
                strlcat (nat_lan_dev_list, br_name, sizeof(nat_lan_dev_list));
            }
            /* Get dmac */
            devmac = (uint8_t *)(br_port_get_rcu(nat_dev)->br->dev->dev_addr);
        }
#else
        if (NULL != nat_dev->br_port) /* under bridge interface. */
        {
            /* Get bridge interface name */
            br_name = (char *)nat_dev->br_port->br->dev->name;
            //memcpy (nat_bridge_dev, br_name, sizeof(br_name));
            strcat (nat_lan_dev_list, " ");
            strcat (nat_lan_dev_list, br_name);
            /* Get dmac */
            devmac = (uint8_t *)nat_dev->br_port->br->dev->dev_addr;
        }
#endif
        else
#endif /* CONFIG_BRIDGE */
        {
            devmac = (uint8_t *)nat_dev->dev_addr;
        }
        /* get vid */
#if 0
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
        vid = vlan_dev_vlan_id(nat_dev);
#else
        vid = 0;
#endif
#endif
		if(is_wan)
			vid = nat_wan_vid;
		else
			vid = nat_lan_vid;
#ifdef CONFIG_IPV6_HWACCEL
        ipv6 = 1;
        if (is_wan)
        {
            wan_fid = vid;
        }
#else
        ipv6 = 0;
        if (is_wan)
        {
            if (NF_S17_WAN_TYPE_PPPOEV6 == nf_athrs17_hnat_wan_type)
                ipv6 = 1;
            wan_fid = vid;
        }
#endif
#ifdef ISISC
        if (0 == is_wan) /* Not WAN -> LAN */
        {
            /* Setup private and netmask as soon as possible */
            if (br_port_get_rcu(nat_dev)) /* under bridge interface. */
            {
                in_device_lan = (struct in_device *) (br_port_get_rcu(nat_dev)->br->dev->ip_ptr);
            }
            else
            {
                in_device_lan = (struct in_device *) nat_dev->ip_ptr;
            }

            if ((in_device_lan) && (in_device_lan->ifa_list))
            {
                nat_hw_prv_mask_set((a_uint32_t)(in_device_lan->ifa_list->ifa_mask));
                nat_hw_prv_base_set((a_uint32_t)(in_device_lan->ifa_list->ifa_address));
#ifndef KVER32
                printk("Set private base 0x%08x for %s\n", (a_uint32_t)(in_device_lan->ifa_list->ifa_address), nat_dev->br_port->br->dev->name);
#endif
                memcpy(&lanip, (void *)&(in_device_lan->ifa_list->ifa_address), 4); /* copy Lan port IP. */
		memcpy(&lanipmask, (void *)&(in_device_lan->ifa_list->ifa_mask), 4);
#ifndef ISISC
                redirect_internal_ip_packets_to_cpu_on_wan_add_acl_rules((a_uint32_t)(in_device_lan->ifa_list->ifa_address),
                                                                            (a_uint32_t)(in_device_lan->ifa_list->ifa_mask));
#endif
            }

            if(setup_lan_if) {
                dev_put(nat_dev);
                rcu_read_unlock();
                return SW_OK;
            } else {
                setup_lan_if = 1;
            }
        }
#endif
        if (1 == is_wan) {
            if (br_port_get_rcu(nat_dev)) {
                in_device_wan = (struct in_device *) (br_port_get_rcu(nat_dev)->br->dev->ip_ptr);
            } else {
                in_device_wan = (struct in_device *) nat_dev->ip_ptr;
            }
            if((nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_IP) &&
               (in_device_wan) && (in_device_wan->ifa_list))
            {
                a_uint32_t index;

                if (((nat_chip_ver&0xffff) >> 8) == DESS_CHIP_VER) {
                    a_uint32_t ip, len;

                    ip = in_device_wan->ifa_list->ifa_address & in_device_wan->ifa_list->ifa_mask;
                    ip = ntohl(ip);
                    len = 32 - ffs(ntohl(in_device_wan->ifa_list->ifa_mask));
                    hnat_add_host_route(ip, len);
                }
                nat_hw_pub_ip_add(ntohl((a_uint32_t)(in_device_wan->ifa_list->ifa_address)),
                                                &index);
                HNAT_PRINTK("pubip add 0x%x\n", (a_uint32_t)(in_device_wan->ifa_list->ifa_address));
        	}
        }
        memcpy(if_mac_addr, devmac, MAC_LEN);
        devmac = if_mac_addr;
        dev_put(nat_dev);

        if(if_mac_add(devmac, vid, ipv6) != 0)
        {
            setup_error = SW_FAIL;
            continue;
        }
        else
        {
            setup_error = SW_OK;
            break;
        }
    }

    rcu_read_unlock();
    return setup_error;
}

static void setup_dev_list(void)
{
	fal_vlan_t entry;
	uint32_t tmp_vid = 0xffffffff;

	/*get the vlan entry*/
	while(1) {
		if(SW_OK != VLAN_NEXT(0, tmp_vid, &entry))
			break;
		tmp_vid = entry.vid;
		if(tmp_vid != 0) {
			if(entry.mem_ports & nat_wan_port) {
				/*wan port*/
				HNAT_PRINTK("wan port vid:%d\n", tmp_vid);
				nat_wan_vid = tmp_vid;
				snprintf(nat_wan_dev_list, IFNAMSIZ*5,
					 "eth0.%d eth1.%d eth0 pppoe-wan erouter0 br-wan",
					 tmp_vid, tmp_vid);
			} else {
				/*lan port*/
				HNAT_PRINTK("lan port vid:%d\n", tmp_vid);
				nat_lan_vid = tmp_vid;
				snprintf(nat_lan_dev_list, IFNAMSIZ*4, "eth0.%d eth1.%d eth1 br-lan",
					tmp_vid, tmp_vid);
			}
		}
	}
}

static int setup_all_interface_entry(void)
{
    //static int setup_lan_if=0;
    static int setup_default_vid = 0;
    int i = 0;

	setup_dev_list();

    if (0 == setup_default_vid)
    {
        for (i=0; i<7; i++) /* For AR8327/AR8337, only 7 port */
        {
#ifdef NAT_TODO /* need to implement here */
            PORTVLAN_ROUTE_DEFV_SET(0, i);
#endif
        }
        setup_default_vid = 1;
    }

    //if (0 == setup_lan_if)
    {
#ifdef ISISC
        //MISC_ARP_CMD_SET(0, FAL_MAC_FRWRD); /* Should be put in init function. */
#if 0
        MISC_ARP_SP_NOT_FOUND_SET(0, FAL_MAC_RDT_TO_CPU);
#endif
#endif
        if (SW_OK == setup_interface_entry(nat_lan_dev_list, 0))
        {
            //setup_lan_if = 1; /* setup LAN interface entry success */
            //printk("Setup LAN interface entry done!\n");
        }
    }

    if (0 == setup_wan_if)
    {
        if (SW_OK == setup_interface_entry(nat_wan_dev_list, 1))
        {
            setup_wan_if = 1; /* setup WAN interface entry success */
        }
    }
	if (((nat_chip_ver&0xffff)>>8) == NAT_CHIP_VER_8327) {
		if ((nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOE) ||
			(nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6))
		{
			uint8_t buf[6];

			memcpy(buf, nf_athrs17_hnat_ppp_peer_mac, ETH_ALEN);
			HNAT_PRINTK("Peer MAC: %s ", buf);
			/* add the peer interface with VID */
			if_mac_add(buf, wan_fid, 0);
			HNAT_PRINTK(" --> (%.2x-%.2x-%.2x-%.2x-%.2x-%.2x)\n", \
				buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
			memcpy(&wanip, (void *)&nf_athrs17_hnat_wan_ip, 4);
		}
	}

    return 1;
}

/* check for pppoe session change */
static void isis_pppoe_check_for_redial(void)
{
    if (nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_IP)
        return;

    if(((nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOE) \
            || (nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6)) \
            && (pppoetbl.session_id != 0))
    {
        if(pppoetbl.session_id != nf_athrs17_hnat_ppp_id)
        {
            aos_printk("%s: PPPoE session ID changed... \n", __func__);
            if (nf_athrs17_hnat_wan_type != NF_S17_WAN_TYPE_PPPOEV6)
            {
                if (PPPOE_SESSION_TABLE_DEL(0, &pppoetbl) != SW_OK)
                {
                    aos_printk("delete old pppoe session %d entry_id %d failed.. \n", pppoetbl.session_id, pppoetbl.entry_id);
                    return;
                }

                /* force PPPoE parser for multi- and uni-cast packets; for v1.0.7+ */
                pppoetbl.session_id = nf_athrs17_hnat_ppp_id;
                pppoetbl.multi_session = 1;
			if (((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_8327)
				pppoetbl.uni_session = 1;
			else
                		pppoetbl.uni_session = 0;
                pppoetbl.entry_id = 0;
                /* set the PPPoE edit reg (0x2200), and PPPoE session reg (0x5f000) */
                if (PPPOE_SESSION_TABLE_ADD(0, &pppoetbl) == SW_OK)
                {
                    PPPOE_SESSION_ID_SET(0, pppoetbl.entry_id, pppoetbl.session_id);
                    printk("%s: new pppoe session id: %x, entry_id: %x\n", __func__, pppoetbl.session_id, pppoetbl.entry_id);
                }
            }
            else  /* nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6 */
            {
                /* reset the session Id only */
                aos_printk("IPV6 PPPOE mode... \n");
                pppoetbl.session_id = nf_athrs17_hnat_ppp_id;
                PPPOE_SESSION_ID_SET(0, pppoetbl.entry_id, pppoetbl.session_id);
                printk("%s: new pppoe session id: %x, entry_id: %x\n", __func__, pppoetbl.session_id, pppoetbl.entry_id);
            }
            /* read back the WAN IP */
            memcpy(&wanip, (void *)&nf_athrs17_hnat_wan_ip, 4);
            aos_printk("Read the WAN IP back... %.8x\n", *(uint32_t *)&wanip);
            /* change the PPPoE ACL to ensure the packet is correctly forwarded by the HNAT engine */
            pppoe_add_acl_rules(*(uint32_t *)&wanip, *(uint32_t *)&lanip,
            						*(uint32_t *)&lanipmask, pppoe_gwid);
        }
    }
}

#ifdef ISIS /* only for S17 */
static void pppoev6_mac6_loop_dev(void)
{
#define PPPOEV6_SESSION_ID  0xfffe
    fal_pppoe_session_t ptbl;
	sw_error_t rv;
	a_uint32_t entry;

    memset(&ptbl, 0, sizeof(fal_pppoe_session_t));

    aos_printk("%s: set MAC6 as loopback device\n", __func__);

    ptbl.session_id = PPPOEV6_SESSION_ID;
    ptbl.multi_session = 1;
    ptbl.uni_session = 1;
    ptbl.entry_id = 0xe;

    /* set the PPPoE edit reg (0x2200), and PPPoE session reg (0x5f000) */
    if (PPPOE_SESSION_TABLE_ADD(0, &ptbl) == SW_OK)
    {
        PPPOE_SESSION_ID_SET(0, ptbl.entry_id, ptbl.session_id);
        aos_printk("%s: pppoe session id: %d added into entry: %d \n", __func__, ptbl.session_id, ptbl.entry_id);
    }
    else
    {
        aos_printk("%s: failed on adding pppoe session id: %d\n", __func__, ptbl.session_id);
    }

    /* PPPoE entry 0 */
	entry = PPPOEV6_SESSION_ID;
	REG_ENTRY_SET(rv, 0, PPPOE_EDIT, 0, (a_uint8_t *) (&entry), sizeof (a_uint32_t));

    aos_printk("%s: end of function... \n", __func__);
}

static void pppoev6_remove_parser(uint32_t entry_id)
{
	sw_error_t rv;
	a_uint32_t entry;
	
    aos_printk("%s: clear entry id: %d\n", __func__, entry_id);
    /* clear the session id in the PPPoE parser engine */
	entry = 0;
	REG_ENTRY_SET(rv, 0, PPPOE_SESSION,
					entry_id, (a_uint8_t *) (&entry), sizeof (a_uint32_t));
}

#if 0
static void pppoev6_mac6_stop_learning(void)
{
    /* do not disable this port if some other registers are already filled in
       to prevent setting conflict */
    int val = S17_P6PAD_MODE_REG_VALUE;
	sw_error_t rv;
	a_uint32_t entry;

    if ( val != (1<<24))
    {
        aos_printk("%s: MAC 6 already being used!\n", __FUNCTION__);
        return;
    }


    /* clear the MAC6 learning bit */
	REG_ENTRY_GET(rv, 0, PORT_LOOKUP_CTL, 6, (a_uint8_t *) (&entry), sizeof (a_uint32_t));
	entry = entry & ~(1<<20);
	REG_ENTRY_SET(rv, 0, PORT_LOOKUP_CTL, 6, (a_uint8_t *) (&entry), sizeof (a_uint32_t));

    /* force loopback mode */
	entry = 0x7e;
	REG_ENTRY_SET(rv, 0, PORT_STATUS, 6, (a_uint8_t *) (&entry), sizeof (a_uint32_t));
	entry = 0x10;
	REG_ENTRY_SET(rv, 0, PORT_HDR_CTL, 6, (a_uint8_t *) (&entry), sizeof (a_uint32_t));
}
#endif
#endif // ifdef ISIS

static int add_pppoe_host_entry(uint32_t sport, a_int32_t arp_entry_id)
{
    a_bool_t ena;
    int rv = SW_OK;
    fal_host_entry_t nh_arp_entry;

    if (0xffff == wan_fid)
    {
        printk("%s: Cannot get WAN vid!\n", __FUNCTION__);
        return SW_FAIL;
    }

    if (PPPOE_STATUS_GET(0, &ena) != SW_OK)
    {
        aos_printk("Cannot get the PPPoE mode\n");
        ena = 0;
    }

    memset(&nh_arp_entry, 0, sizeof(nh_arp_entry));
    nh_arp_entry.ip4_addr = ntohl(nf_athrs17_hnat_ppp_peer_ip);
    nh_arp_entry.flags = FAL_IP_IP4_ADDR;
    rv = IP_HOST_GET(0, FAL_IP_ENTRY_IPADDR_EN, &nh_arp_entry);
    if (SW_OK != rv || pppoetbl.session_id != nf_athrs17_hnat_ppp_id)
    {
        if ((!ena) && (PPPOE_STATUS_SET(0, A_TRUE) != SW_OK))
            aos_printk("Cannot enable the PPPoE mode\n");

        aos_printk("PPPoE enable mode: %d\n", ena);

        pppoetbl.session_id = nf_athrs17_hnat_ppp_id;
        pppoetbl.multi_session = 1;
		if (((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_8327)
			pppoetbl.uni_session = 1;
		else
        		pppoetbl.uni_session = 0;
        pppoetbl.entry_id = 0;

        /* set the PPPoE edit reg (0x2200), and PPPoE session reg (0x5f000) */
        rv = PPPOE_SESSION_TABLE_ADD(0, &pppoetbl);
        if (rv == SW_OK)
        {
            uint8_t mbuf[6], ibuf[4];
            a_int32_t a_entry_id = -1;
			a_uint32_t index;

            PPPOE_SESSION_ID_SET(0, pppoetbl.entry_id, pppoetbl.session_id);
            aos_printk("pppoe session: %d, entry_id: %d\n", pppoetbl.session_id, pppoetbl.entry_id);

            if (((nat_chip_ver&0xffff)>>8) == DESS_CHIP_VER) {
                hnat_add_host_route(ntohl(nf_athrs17_hnat_wan_ip),
				HOST_PREFIX_MAX);
            }

            /* create the peer host ARP entry */
            memcpy(ibuf, (void *)&nf_athrs17_hnat_ppp_peer_ip, 4);
            memcpy(mbuf, nf_athrs17_hnat_ppp_peer_mac, ETH_ALEN);

            a_entry_id = arp_hw_add(S17_WAN_PORT, wan_fid, ibuf, mbuf, 0);
            if (a_entry_id >= 0) /* hostentry creation okay */
            {
                aos_printk("(1)Bind PPPoE session ID: %d, entry_id: %d to host entry: %d\n", \
                           pppoetbl.session_id, pppoetbl.entry_id, a_entry_id);

                rv = IP_HOST_PPPOE_BIND(0, a_entry_id, pppoetbl.entry_id, A_TRUE);
                if ( rv != SW_OK)
                {
                    aos_printk("IP_HOST_PPPOE_BIND failed (entry: %d, rv: %d)... \n", a_entry_id, rv);
                }

                aos_printk("adding ACLs \n");
                pppoe_gwid = a_entry_id;
			pppoe_add_acl_rules(nf_athrs17_hnat_wan_ip, *(uint32_t *)&lanip,
								*(uint32_t *)&lanipmask, a_entry_id);
			nat_hw_pub_ip_add(ntohl(nf_athrs17_hnat_wan_ip), &index);
                aos_printk("ACL creation okay... \n");
            } else {
			HNAT_PRINTK("pppoe arp add fail!\n");
            }
        }
        else
        {
            aos_printk("PPPoE session add failed.. (id: %d)\n", pppoetbl.session_id);
            aos_printk("rv: %d\n", rv);
        }

#ifdef ISIS
        if (nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6)
        {
            aos_printk("IPV6 PPPOE mode... (share the same ID with IPV4's)\n");
            pppoev6_mac6_loop_dev();
            pppoev6_remove_parser(pppoetbl.entry_id);

            /* bind the first LAN host to the pseudo PPPoE ID */
            rv = IP_HOST_PPPOE_BIND(0, arp_entry_id, 0, A_TRUE);
            if ( rv != SW_OK)
            {
                aos_printk("IP_HOST_PPPOE_BIND failed (entry: %d, rv: %d)... \n", arp_entry_id, rv);
            }
        }
#endif // ifdef ISIS
    }
#ifdef ISIS
    else  /* ena */
    {
        if ((nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6) &&
                (sport != S17_WAN_PORT)&& (arp_entry_id != 0))
        {
            aos_printk("IPV6 PPPoE mode\n");
            /* bind LAN hosts to the pseudo PPPoE ID */
            rv = IP_HOST_PPPOE_BIND(0, arp_entry_id, 0, A_TRUE);
            if ( rv != SW_OK)
            {
                aos_printk("IP_HOST_PPPOE_BIND failed (entry: %d, rv: %d)... \n", arp_entry_id, rv);
            }
        }
    }
#endif // ifdef ISIS

    return SW_OK;
}

static int
dev_check(char *in_dev, char *dev_list)
{
    char *list_dev;
    char temp[100] = {0};
    char *list;

    if(!in_dev || !dev_list)
    {
        return 0;
    }

    strlcpy(temp, dev_list, sizeof(temp));
    list = temp;

    while ((list_dev = strsep(&list, " ")) != NULL)
    {
        HNAT_PRINTK("%s: strlen:%d list_dev:%s in_dev:%s\n",
                    __func__, strlen(list_dev), list_dev, in_dev);

        if (!strncmp(list_dev, in_dev, strlen(list_dev)))
        {
            HNAT_PRINTK("%s: %s\n", __FUNCTION__, list_dev);
            return 1;
        }
    }

    return 0;
}

#ifndef ISISC
static uint32_t get_netmask_from_netdevice(const struct net_device *in_net_dev)
{
    struct in_device *my_in_device = NULL;
    uint32_t result = 0xffffff00;

    if((in_net_dev) && (in_net_dev->ip_ptr != NULL))
    {
        my_in_device = (struct in_device *)(in_net_dev->ip_ptr);
        if(my_in_device->ifa_list != NULL)
        {
            result = my_in_device->ifa_list->ifa_mask;
        }
    }

    return result;
}
#endif


static void hnat_add_neigh(struct neighbour *neigh) 
{
	struct nat_helper_bg_msg msg;
	struct net_device *dev = NULL;

	memset(&msg, 0, sizeof(msg));
	msg.arp_in.ip = *((uint32_t *)neigh->primary_key);
	memcpy(msg.arp_in.mac, neigh->ha, ETH_ALEN);
	strlcpy(msg.arp_in.name, neigh->dev->name, IFNAMSIZ);
	msg.arp_in.in = neigh->dev;

	if (neigh->dev->priv_flags & IFF_EBRIDGE) {
		if (!(dev = br_port_dev_get(neigh->dev, neigh->ha, NULL, 0))) {
			HNAT_ERR_PRINTK("Failed to find bridge port by [%pM]\n",
				neigh->ha);
			return ;
		}
	} else {
		dev = neigh->dev;
		dev_hold(dev);
	}

	if (strncmp(dev->name, "eth", strlen("eth")) &&
	     strncmp(dev->name, "erouter", strlen("erouter"))) {
		dev_put(dev);
		return ;
	}

	dev_put(dev);

	msg.msg_type = NAT_HELPER_ARP_ADD_MSG;
	bg_ring_buf_write(msg);
}

static void hnat_del_neigh(struct neighbour *neigh)
{
	struct nat_helper_bg_msg msg;

	memset(&msg, 0, sizeof(msg));
	msg.arp_in.ip  = ntohl(*((uint32_t *)neigh->primary_key));

	msg.msg_type = NAT_HELPER_ARP_DEL_MSG;
	bg_ring_buf_write(msg);
}

static int hnat_netevent_event(struct notifier_block *unused, unsigned long event, void *ptr)
{
	struct neigh_table *tbl;
	struct neighbour *neigh;

	if (event != NETEVENT_NEIGH_UPDATE)
		return NOTIFY_DONE;

	neigh = ptr;
	tbl = neigh->tbl;

	if (tbl->family != AF_INET)
		return NOTIFY_DONE;

	HNAT_PRINTK("netevent state %d for ip[%pI4]\n",
		neigh->nud_state, (uint32_t *)neigh->primary_key);

	if (neigh->nud_state & NUD_VALID) {
		if (neigh->nud_state & NUD_NOARP) {
			return NOTIFY_DONE;
		}

		HNAT_PRINTK("New ARP entry ip[%pI4] mac[%pM]\n",
			(uint32_t *)neigh->primary_key, neigh->ha);
		hnat_add_neigh(neigh);
	} else {
		HNAT_PRINTK("Del ARP entry ip[%pI4] mac[%pM] with status[%d]\n",
			(uint32_t *)neigh->primary_key, neigh->ha, neigh->nud_state);
		hnat_del_neigh(neigh);
	}

	return NOTIFY_DONE;	
}

#ifdef NAT_BACKGROUND_TASK
static unsigned int
arp_del(struct nat_helper_bg_msg *msg)
{
	fal_host_entry_t host;

	memset(&host, 0, sizeof(host));

	if (!nat_hw_prv_base_is_match(msg->arp_in.ip))
		return 0;

	if (napt_hw_get_by_sip(msg->arp_in.ip)) {
		host.flags |= FAL_IP_IP4_ADDR;
		host.ip4_addr = msg->arp_in.ip;
		IP_HOST_DEL(0, FAL_IP_ENTRY_IPADDR_EN, &host);
	}

	return 0;
}

static unsigned int
arp_add(struct nat_helper_bg_msg *msg)
{
    uint8_t *smac;
    uint8_t dev_is_lan = 0;
    uint32_t sport = 0, vid = 0;
    a_bool_t prvbasemode = 1;
    sw_error_t rv = SW_OK;
    struct arp_in_msg *arp_info = &msg->arp_in;
    a_int32_t arp_entry_id = -1;
    fal_fdb_entry_t entry;


    /* check for PPPoE redial here, to reduce overheads */
    isis_pppoe_check_for_redial();

    /* do not write out host table if HNAT is disabled */
    if (!nf_athrs17_hnat)
        return 0;

    setup_all_interface_entry();

    if(dev_check((char *)arp_info->name, (char *)nat_wan_dev_list))
    {

    }
    else if (dev_check((char *)arp_info->name, (char *)nat_lan_dev_list))
    {
        dev_is_lan = 1;
    }
    else
    {
        HNAT_INFO_PRINTK("Not Support device: %s\n",  (char *)arp_info->name);
        return 0;
    }

    if(dev_is_lan) {
         vid = nat_lan_vid;
    } else {
         vid = nat_wan_vid;
    }

    memset(&entry, 0, sizeof(entry));

    entry.fid = vid;

    smac = arp_info->mac;
    aos_mem_copy(&(entry.addr), smac, sizeof(fal_mac_addr_t));
    if(FDB_ENTRY_SEARCH(0, &entry) == SW_OK) {
        vid  = entry.fid;
        sport = 0;
        while (sport < 32) {
            if(entry.port.map & (1 << sport)) {
                break;
            }
            sport++;
        }
    } else {
        HNAT_PRINTK("not find the FDB entry\n");
    }


    if (sport == 0) {
        HNAT_PRINTK("Not the expected arp, ignore it!\n");
        return 0;
    }

    arp_entry_id = arp_hw_add(sport, vid, (a_uint8_t *)&arp_info->ip, smac, 0);
    if(arp_entry_id < 0)
    {
        HNAT_ERR_PRINTK("ARP entry error!!\n");
        return 0;
    }

    if (0 == dev_is_lan)
    {
        struct in_device *in_dev;

        in_dev = __in_dev_get_rtnl(arp_info->in);
        if (in_dev) {
            if (in_dev->ifa_list) {
                *(uint32_t *)&wanip = ntohl(in_dev->ifa_list->ifa_local);
            }
        }
#ifdef MULTIROUTE_WR
        wan_nh_add((u_int8_t *)&arp_info->ip, smac, arp_entry_id);
#endif
    }

    if(dev_is_lan && nat_hw_prv_base_can_update())
    {
        nat_hw_flush();
        nat_hw_prv_base_update_disable();
#ifdef MULTIROUTE_WR
        //multi_route_indev = in;
#endif
    }
    multi_route_indev = arp_info->in;

    if ((nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOE) ||
            (nf_athrs17_hnat_wan_type == NF_S17_WAN_TYPE_PPPOEV6))
    {
        add_pppoe_host_entry(sport, arp_entry_id);
    }

	if (((nat_chip_ver & 0xffff)>>8) != NAT_CHIP_VER_8327)
		return 1;

    /* check for SIP and DIP range */
    if ((lanip[0] != 0) && (wanip[0] != 0))
    {
	 rv = NAT_PRV_ADDR_MODE_GET(0, &prvbasemode);
	 if (rv == SW_NOT_SUPPORTED || rv == SW_NOT_INITIALIZED) {
		 return 1;
	 }
	 else if (rv != SW_OK) {
		 aos_printk("Private IP base mode check failed: %d\n", prvbasemode);
	 }

        if (!prvbasemode) /* mode 0 */
        {
            if ((lanip[0] == wanip[0]) && (lanip[1] == wanip[1]))
            {
                if ((lanip[2] & 0xf0) == (wanip[2] & 0xf0))
                {
                    if (get_aclrulemask()& (1 << S17_ACL_LIST_IPCONF))
                        return 0;

                    aos_printk("LAN IP and WAN IP conflict... \n");
                    /* set h/w acl to filter out this case */
#ifdef MULTIROUTE_WR
                    // if ( (wan_nh_ent[0].host_ip != 0) && (wan_nh_ent[0].entry_id != 0))
                    if ( (wan_nh_ent[0].host_ip != 0))
                        ip_conflict_add_acl_rules(*(uint32_t *)&wanip, *(uint32_t *)&lanip, wan_nh_ent[0].entry_id);
#endif
                    return 0;
                }
            }
        }
        else  /* mode 1*/
        {
            ;; /* do nothing */
        }
    }

    return 1;
}
#endif

static struct notifier_block hnat_netevent_notifier = {
	.notifier_call = hnat_netevent_event,
};

#ifdef AUTO_UPDATE_PPPOE_INFO
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
struct prv_ppp_file {
	int		k;
	struct sk_buff_head xmitq;
	struct sk_buff_head recvq;
	wait_queue_head_t wait;
	atomic_t	cnt;
	int		hlen;
	int		idx;
	int		d;
};
struct prv_ppp {
	struct prv_ppp_file	file;
	char	*o;
	struct list_head channels;
	int		n_channels;
	spinlock_t	rlock;
	spinlock_t	wlock;
	int		m;
	unsigned int	flags;
	unsigned int	xmitstate;
	unsigned int	recvstate;
	int		d;
	char *vj;
	int	mode[6];
	struct sk_buff	*xpending;
	char *xcmp;
	void		*xstate;
	char *rcmp;
	void		*rstate;
	unsigned long	l_xmit;
	unsigned long	l_recv;
	struct net_device *dev;
	int		resv1;
#ifdef CONFIG_PPP_MULTILINK
	int		resv2;
	u32		resv3;
	int		resv4;
	u32		resv5;
	u32		resv6;
	struct sk_buff_head rq;
#endif /* CONFIG_PPP_MULTILINK */
#ifdef CONFIG_PPP_FILTER
	char *p_filter;
	char *a_filter;
	unsigned p_len, a_len;
#endif /* CONFIG_PPP_FILTER */
	char	*pnet;
};

struct prv_channel {
	struct prv_ppp_file	file;
	struct list_head list;
	struct ppp_channel *chan;
	struct rw_semaphore sem;
	spinlock_t	downlock;
	struct prv_ppp	*ppp;
	char	*cnet;
	struct list_head clist;
	rwlock_t	uplock;
#ifdef CONFIG_PPP_MULTILINK
	u8		resv1;
	u8		resv2;
	u32		resv3;
	int		resv4;
#endif
};
#endif

static int qcaswitch_pppoe_ip_event(struct notifier_block *this,
                                    unsigned long event, void *ptr)
{
    struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
    struct net_device *dev = (struct net_device *)ifa->ifa_dev->dev;
	#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0))
	struct list_head *list;
	struct prv_channel *pch;
	struct prv_ppp *ppp = netdev_priv(dev);
	#endif
    struct sock *sk;
    struct pppox_sock *po;
    static int connection_count = 0;
    fal_pppoe_session_t del_pppoetbl;
	int channel_count;
	struct ppp_channel *ppp_chan[1];
	int channel_protocol;

    if (!((dev->type == ARPHRD_PPP) && (dev->flags & IFF_POINTOPOINT)))
        return NOTIFY_DONE;

    if (dev_net(dev) != &init_net)
        return NOTIFY_DONE;

    setup_all_interface_entry();

    switch (event)
    {
        case NETDEV_UP:
	#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
            if (ppp_is_multilink(dev) == 0) {
                /* not doing multilink: send it down the first channel */
			channel_count = ppp_hold_channels(dev, ppp_chan, 1);
			if (channel_count != 1)
				return NOTIFY_DONE;

			channel_protocol = ppp_channel_get_protocol(ppp_chan[0]);
			if (channel_protocol == PX_PROTO_OE)
			{
				if (ppp_chan[0]->private)
				{
					/* the NETDEV_UP event will be sent many times
					* because of ifa operation ifa->ifa_local != ifa->ifa_address
					* means that remote ip is really added.
					*/
					if (ifa->ifa_local == ifa->ifa_address)
					{
						ppp_release_channels(ppp_chan, 1);
						return NOTIFY_DONE;
					}
					sk = (struct sock *)(ppp_chan[0]->private);
					po = (struct pppox_sock*)sk;
					connection_count++;
					if (((NF_S17_WAN_TYPE_PPPOE == nf_athrs17_hnat_wan_type) &&
					(0 != nf_athrs17_hnat_ppp_id))) /* another session for IPv6 */
					{
						nf_athrs17_hnat_ppp_id2 = ntohs(po->proto.pppoe.pa.sid);
						memcpy(nf_athrs17_hnat_ppp_peer_mac2,
							po->proto.pppoe.pa.remote, ETH_ALEN);
					} else {
						nf_athrs17_hnat_wan_type = NF_S17_WAN_TYPE_PPPOE;
						nf_athrs17_hnat_wan_ip = ifa->ifa_local;
						nf_athrs17_hnat_ppp_peer_ip = ifa->ifa_address;
						memcpy(nf_athrs17_hnat_ppp_peer_mac,
							po->pppoe_pa.remote, ETH_ALEN);
						nf_athrs17_hnat_ppp_id = ntohs(po->pppoe_pa.sid);
					}
					ppp_release_channels(ppp_chan, 1);
				} else {
					ppp_release_channels(ppp_chan, 1);
					/* channel got unregistered */
					return NOTIFY_DONE;
				}
			} else {
				ppp_release_channels(ppp_chan, 1);
				/* channel got unregistered */
				return NOTIFY_DONE;
			}
		}
	#else
		//struct prv_ppp *ppp = netdev_priv(dev);
		//struct prv_channel *pch;
		list = &ppp->channels;
		if (list_empty(list))
                return NOTIFY_DONE;
		if ((ppp->flags & SC_MULTILINK) == 0) {
			list = list->next;
			pch = list_entry(list, struct prv_channel, clist);
			if (pch->chan)
			{
				if (pch->chan->private) {
					if (ifa->ifa_local == ifa->ifa_address)
						return NOTIFY_DONE;
					sk = (struct sock *)pch->chan->private;
					po = (struct pppox_sock*)sk;
					connection_count++;
					if (((NF_S17_WAN_TYPE_PPPOE == nf_athrs17_hnat_wan_type) &&
						(0 != nf_athrs17_hnat_ppp_id)))
					{
						nf_athrs17_hnat_ppp_id2 = po->num;
						memcpy(nf_athrs17_hnat_ppp_peer_mac2, po->pppoe_pa.remote, ETH_ALEN);
					}
					else
					{
						nf_athrs17_hnat_wan_type = NF_S17_WAN_TYPE_PPPOE;
						nf_athrs17_hnat_wan_ip = ifa->ifa_local;
		                            nf_athrs17_hnat_ppp_peer_ip = ifa->ifa_address;
		                            memcpy(nf_athrs17_hnat_ppp_peer_mac, po->pppoe_pa.remote, ETH_ALEN);
		                            nf_athrs17_hnat_ppp_id = ntohs(po->pppoe_pa.sid);
					}
				}
			}
			else
			{
				return NOTIFY_DONE;
			}
		}
	#endif
		break;

        case NETDEV_DOWN:
            if (NF_S17_WAN_TYPE_PPPOE != nf_athrs17_hnat_wan_type)
            {
                return NOTIFY_DONE;
            }
            connection_count--;
            if (ifa->ifa_local == nf_athrs17_hnat_wan_ip)
            {
                /* PPPoE Interface really down */
                ipv6_droute_del_acl_rules();
                del_pppoetbl.session_id = nf_athrs17_hnat_ppp_id;
                del_pppoetbl.multi_session = 1;
			if (((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_8327)
				del_pppoetbl.uni_session = 1;
			else
                		del_pppoetbl.uni_session = 0;
                del_pppoetbl.entry_id = 0;
                PPPOE_SESSION_TABLE_DEL(0, &del_pppoetbl);
		memset(&pppoetbl, 0, sizeof(pppoetbl));
                nf_athrs17_hnat_wan_type = NF_S17_WAN_TYPE_IP;
                nf_athrs17_hnat_wan_ip = 0;
                nf_athrs17_hnat_ppp_peer_ip = 0;
                nf_athrs17_hnat_ppp_id = 0;
                memset(&nf_athrs17_hnat_ppp_peer_mac, 0, ETH_ALEN);
            }
            else
            {
                if (0 != nf_athrs17_hnat_ppp_id2)
                {
                    del_pppoetbl.session_id = nf_athrs17_hnat_ppp_id2;
                    del_pppoetbl.multi_session = 1;
				if (((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_8327)
					del_pppoetbl.uni_session = 1;
				else
                    		del_pppoetbl.uni_session = 0;
                    del_pppoetbl.entry_id = 0;
                    PPPOE_SESSION_TABLE_DEL(0, &del_pppoetbl);
			memset(&pppoetbl, 0, sizeof(pppoetbl));
                }
                nf_athrs17_hnat_ppp_id2 = 0;
                memset(&nf_athrs17_hnat_ppp_peer_mac2, 0, ETH_ALEN);
            }
            qcaswitch_hostentry_flush();
            break;

        default:
            break;
    }
    return NOTIFY_DONE;
}

/* a linux interface is configured with ipaddr, then
 * it becomes a L3 routing interface
 * add the router mac of this interface to the table
 */
/* FIXME: only hande pppoe event right now. */
static int qcaswitch_ip_event(struct notifier_block *this,
                              unsigned long event, void *ptr)
{
    struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
    struct net_device *dev = (struct net_device *)ifa->ifa_dev->dev;

    if ((dev->type == ARPHRD_PPP) && (dev->flags & IFF_POINTOPOINT))
    {
        return qcaswitch_pppoe_ip_event(this, event, ptr);
    }

    return NOTIFY_DONE;
}


static struct notifier_block qcaswitch_ip_notifier =
{
    .notifier_call = qcaswitch_ip_event,
    .priority = 100,
};
#endif // ifdef AUTO_UPDATE_PPPOE_INFO

#define HOST_AGEOUT_STATUS 1
void host_check_aging(void)
{
    fal_host_entry_t *host_entry_p, host_entry= {0};
    sw_error_t rv;
    int cnt = 0;
    unsigned long flags;
    fal_napt_entry_t src_napt = {0}, pub_napt = {0};

    host_entry_p = &host_entry;
    host_entry_p->entry_id = FAL_NEXT_ENTRY_FIRST_ID;

	/*check host is not neccessary, check napt is enough*/
	return;

    local_irq_save(flags);
    while (1)
    {
        host_entry_p->status = HOST_AGEOUT_STATUS;
        /* FIXME: now device id is set to 0. */
        rv = IP_HOST_NEXT (0, FAL_IP_ENTRY_STATUS_EN, host_entry_p);
        // rv = IP_HOST_NEXT (0, 0, host_entry_p);
        if (SW_OK != rv)
            break;
        if (cnt >= ARP_ENTRY_MAX) // arp entry number
            break;

        if (ARP_AGE_NEVER == host_entry_p->status)
            continue;

        if ((S17_WAN_PORT == host_entry_p->port_id) &&
                (host_entry_p->counter_en))
        {
            if (0 != host_entry_p->packet)
            {
                // arp entry is using, update it.
                host_entry.status = ARP_AGE;
                printk("Update WAN port hostentry!\n");
                IP_HOST_ADD(0, host_entry_p);
            }
            else
            {
                printk("Del WAN port hostentry!\n");
                IP_HOST_DEL(0, FAL_IP_ENTRY_IPADDR_EN, host_entry_p);
            }
            continue;
        }

        src_napt.entry_id = FAL_NEXT_ENTRY_FIRST_ID;
        memcpy(&src_napt.src_addr, &host_entry_p->ip4_addr, sizeof(fal_ip4_addr_t));
        pub_napt.entry_id = FAL_NEXT_ENTRY_FIRST_ID;
        memcpy(&pub_napt.trans_addr, &host_entry_p->ip4_addr, sizeof(fal_ip4_addr_t));
        if((NAPT_NEXT(0, FAL_NAT_ENTRY_SOURCE_IP_EN ,&src_napt) !=0) && \
                (NAPT_NEXT(0, FAL_NAT_ENTRY_PUBLIC_IP_EN ,&pub_napt) != 0))
        {
            /* Cannot find naptentry */
            printk("ARP id 0x%x: Cannot find NAPT entry!\n", host_entry_p->entry_id);
            IP_HOST_DEL(0, FAL_IP_ENTRY_IPADDR_EN, host_entry_p);
            continue;
        }
        // arp entry is using, update it.
        host_entry_p->status = ARP_AGE;
        IP_HOST_ADD(0, host_entry_p);
        printk("update entry 0x%x port %d\n", host_entry_p->entry_id, host_entry_p->port_id);
        cnt++;
    }
    local_irq_restore(flags);
}

#ifdef CONFIG_IPV6_HWACCEL
#define IPV6_LEN 16
#define MAC_LEN 6
#define PROTO_ICMPV6 0x3a
#define NEIGHBOUR_SOL 135
#define NEIGHBOUR_AD 136

struct icmpv6_option
{
    __u8 type;
    __u8 len;
    __u8 mac[MAC_LEN];
};
#if 0
static unsigned int ipv6_handle(unsigned   int   hooknum,
                                struct   sk_buff   *skb,
                                const   struct   net_device   *in,
                                const   struct   net_device   *out,
                                int   (*okfn)(struct   sk_buff   *))
{
	struct sk_buff *new_skb = NULL;
	struct nat_helper_bg_msg msg;
	/*unsigned long flags = 0;*/

	if (!nf_athrs17_hnat)
        return NF_ACCEPT;

	new_skb = skb_clone(skb, GFP_ATOMIC);
	if(new_skb) {
		memset(&msg, 0, sizeof(msg));
		msg.msg_type = NAT_HELPER_IPV6_MSG;
		msg.ipv6.skb = new_skb;
		msg.ipv6.in = (struct net_device *)in;

		/*send msgto background task*/
		/*spin_lock_irqsave(&task_cb.bg_lock, flags);*/
		if(bg_ring_buf_write(msg))
			kfree_skb(new_skb);
		/*spin_unlock_irqrestore(&task_cb.bg_lock, flags);*/
	}
	
	return NF_ACCEPT;
	
}
#endif

#ifdef NAT_BACKGROUND_TASK

static unsigned int ipv6_bg_handle(struct nat_helper_bg_msg *msg)
{
	struct net_device *in = msg->arp_in.in;
	struct sk_buff *skb = msg->arp_in.skb;
	
    struct ipv6hdr *iph6 = ipv6_hdr(skb);
    struct icmp6hdr *icmp6 = icmp6_hdr(skb);
    __u8 *sip = ((__u8 *)icmp6)+sizeof(struct icmp6hdr);
    struct icmpv6_option *icmpv6_opt = (struct icmpv6_option *)(sip+IPV6_LEN);
    __u8 *sa = icmpv6_opt->mac;

    uint32_t sport = 0, vid = 0;
    struct inet6_ifaddr *in_device_addr = NULL;
    uint8_t dev_is_lan = 0;
    uint8_t *smac;
	

    /* do not write out host table if HNAT is disabled */
    if (!nf_athrs17_hnat)
        return 0;

    setup_all_interface_entry();

    if(dev_check((char *)in->name, (char *)nat_wan_dev_list))
    {
        dev_is_lan = 0;
    }
    else if (dev_check((char *)in->name, (char *)nat_lan_dev_list))
    {
        dev_is_lan = 1;
    }
    else
    {
        HNAT_PRINTK("Not Support device: %s\n",  (char *)in->name);
        return 0;
    }

    if(PROTO_ICMPV6 == iph6->nexthdr)
    {
        if(NEIGHBOUR_AD == icmp6->icmp6_type)
        {
			fal_fdb_entry_t entry;
            if (__ipv6_addr_type((struct in6_addr*)sip) & IPV6_ADDR_LINKLOCAL)
                return 0;

#ifdef AP136_QCA_HEADER_EN
            if(arp_if_info_get((void *)(skb->head), &sport, &vid) != 0)
            {
                return 0;
            }

            if ((0 == vid)||(0 == sport))
            {
                printk("Error: Null sport or vid!!\n");
                return 0;
            }
#else
            if(dev_is_lan) {
                 vid = NAT_LAN_DEV_VID;
            } else {
                 vid = NAT_WAN_DEV_VID;
            }

            memset(&entry, 0, sizeof(entry));

            entry.fid = vid;
            smac = skb_mac_header(skb) + MAC_LEN;
            aos_mem_copy(&(entry.addr), smac, sizeof(fal_mac_addr_t));

            if(FDB_ENTRY_SEARCH(0, &entry) == SW_OK) {
                vid  = entry.fid;
                sport = 0;
                while (sport < 32) {
                    if(entry.port.map & (1 << sport)) {
                        break;
                    }
                    sport++;
                }
            } else {
                printk("not find the FDB entry\n");
            }
#endif
            if ((0 == dev_is_lan) && (S17_WAN_PORT != sport))
            {
                printk("Error: WAN port %d\n", sport);
                return 0;
            }

            HNAT_PRINTK("ND Reply %x %x\n",icmpv6_opt->type,icmpv6_opt->len);
            HNAT_PRINTK("isis_v6: incoming packet, sip = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n"
                        ,sip[0],sip[1],sip[2],sip[3],sip[4],sip[5],sip[6],sip[7]
                        ,sip[8],sip[9],sip[10],sip[11],sip[12],sip[13],sip[14],sip[15]
                       );
            HNAT_PRINTK("isis_v6: incoming packet, sa  = %.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n", sa[0], sa[1], sa[2], sa[3], sa[4], sa[5]);
            HNAT_PRINTK("isis_v6: vid = %d sport = %d\n", vid, sport);

            //add nd entry
            if((2 == icmpv6_opt->type) && (1 == icmpv6_opt->len))
            {
                arp_hw_add(sport, vid, sip, sa, 1);
            }
            else /* ND AD packets without option filed? Fix Me!! */
            {
		sa = skb_mac_header(skb) + MAC_LEN;
                HNAT_PRINTK("isis_v6 Changed sa  = %.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n", sa[0], sa[1], sa[2], sa[3], sa[4], sa[5]);
                arp_hw_add(sport, vid, sip, sa, 1);
            }

#ifdef NAT_TODO /* should be ok */
            if ((NULL != in->ip6_ptr) && (NULL != ((struct inet6_dev *)in->ip6_ptr)->addr_list))
#else
            if (NULL != in->ip6_ptr)
#endif
            {
                //list_for_each_entry(in_device_addr, &(in->ip6_ptr)->addr_list, if_list);
                struct inet6_dev *idev = __in6_dev_get(in);
                list_for_each_entry(in_device_addr, &idev->addr_list, if_list) {
            		if (in_device_addr->scope == 0 &&
            		    !(in_device_addr->flags & IFA_F_TENTATIVE)) {
            			break;
            		}
        	    }

                if (0 == dev_is_lan)
                {
                    /* WAN ipv6 address*/
                    memcpy(&wan6ip, (__u8 *)&in_device_addr->addr, sizeof(struct in6_addr));
                    HNAT_PRINTK("%s: ipv6 wanip %pI6\n", in->name, &wan6ip);
                }
                else
                {
                    /* LAN ipv6 address*/
                    memcpy(&lan6ip, (__u8 *)&in_device_addr->addr, sizeof(struct in6_addr));
                    HNAT_PRINTK("%s: ipv6 lanip %pI6\n", in->name, &lan6ip);
                }
            }
        }
    }

    return 1;
}
#endif

#if 0
static struct nf_hook_ops ipv6_inhook =
{
    .hook = ipv6_handle,
    #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0))
    .owner = THIS_MODULE,
    #endif
    .pf = PF_INET6,
    .hooknum = NF_INET_PRE_ROUTING,
    .priority = NF_IP6_PRI_CONNTRACK,
};
#endif
#endif /* CONFIG_IPV6_HWACCEL */

#ifdef NAT_BACKGROUND_TASK

static void nat_task_entry(struct work_struct *wq)
{
	struct nat_helper_bg_msg msg;
	/*unsigned long flags = 0;*/
	unsigned int result = 0;

	msg = *(struct nat_helper_bg_msg *)wq;

	/*spin_lock_irqsave(&task_cb.bg_lock, flags);*/
	bg_ring_buf_read(NULL);
	/*spin_unlock_irqrestore(&task_cb.bg_lock, flags);*/

	if(msg.msg_type == NAT_HELPER_ARP_ADD_MSG) {
		result =  arp_add(&msg);
	} else if (msg.msg_type == NAT_HELPER_ARP_DEL_MSG) {
		result =  arp_del(&msg);
	}
	#ifdef CONFIG_IPV6_HWACCEL
	else if(msg.msg_type == NAT_HELPER_IPV6_MSG) {
		result = ipv6_bg_handle(&msg);
	}
	#endif

	HNAT_PRINTK("handle msg: %d, result: %d\n", msg.msg_type, result);
	
}

void nat_helper_bg_task_init()
{
	unsigned int i = 0;
	struct nat_helper_bg_msg *msg;
	/*create the thread and alloc ring buffer*/

	memset(&task_cb, 0, sizeof(task_cb));

	task_cb.nat_wq = create_singlethread_workqueue("nat_wq");

    if(!task_cb.nat_wq)
    {
        aos_printk("create nat workqueuefail\n");
        return;
    }

	/*init lock and alloc the ring buffer*/
	
	/*sema_init(&task_cb.bg_sem, 0);*/
	spin_lock_init(&task_cb.bg_lock);
	
	task_cb.ring.num = NAT_HELPER_MSG_MAX;
	task_cb.ring.buf = kzalloc(NAT_HELPER_MSG_MAX * sizeof(struct nat_helper_bg_msg), GFP_ATOMIC);
	if(!task_cb.ring.buf) {
		aos_printk("ring buf alloc fail!\n");
		return;
	}
	msg = (struct nat_helper_bg_msg*)task_cb.ring.buf;
	for(i = 0; i < task_cb.ring.num; i++) 
	{
		INIT_WORK(&msg[i].work, nat_task_entry);
	}

	aos_printk("bg task init successfull!\n");
	
}

void nat_helper_bg_task_exit()
{
	/*stop the workqueue and release the ring buffer*/
	if(task_cb.nat_wq)
		destroy_workqueue(task_cb.nat_wq);
	if(task_cb.ring.buf)
		kfree(task_cb.ring.buf);
}
#endif


extern int napt_procfs_init(void);
extern void napt_procfs_exit(void);

void host_helper_init(a_uint32_t portbmp)
{
	REG_GET(0, 0, (a_uint8_t *)&nat_chip_ver, 4);

	/* header len 4 with type 0xaaaa */
	HEADER_TYPE_SET(0, A_TRUE, 0xaaaa);
	if (((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_8337 ||
		((nat_chip_ver & 0xffff)>>8) == NAT_CHIP_VER_DESS) {
		/* For S17c (ISISC), it is not necessary to make all frame with header */
		printk("host_helper_init start\n");
		//PORT_TXHDR_MODE_SET(0, 0, FAL_ONLY_MANAGE_FRAME_EN);
		/* Fix tag disappear problem, set TO_CPU_VID_CHG_EN, 0xc00 bit1 */
		CPU_VID_EN_SET(0, A_TRUE);
		/* set RM_RTD_PPPOE_EN, 0xc00 bit0 */
		RTD_PPPOE_EN_SET(0, A_TRUE);
		/* Avoid ARP response storm for HUB, now this fix only apply on PORT5 */
		#if 0
		MISC_ARP_SP_NOT_FOUND_SET(0, FAL_MAC_RDT_TO_CPU);
		MISC_ARP_GUARD_SET(0, S17_WAN_PORT, FAL_MAC_IP_PORT_GUARD);
		#endif
		/* set VLAN_TRANS_TEST register bit, to block packets from WAN port has private dip */
		NETISOLATE_SET(0, A_TRUE);
	} else {
		PORT_TXHDR_MODE_SET(0, 0, FAL_ALL_TYPE_FRAME_EN);
	}
    CPU_PORT_STATUS_SET(0, A_TRUE);
    IP_ROUTE_STATUS_SET(0, A_TRUE);

    napt_procfs_init();
    memcpy(nat_bridge_dev, nat_lan_dev_list, strlen(nat_lan_dev_list)+1);

    register_netevent_notifier(&hnat_netevent_notifier);
/*hnat not upport ipv6*/
#if 0
#ifdef CONFIG_IPV6_HWACCEL
    aos_printk("Registering IPv6 hooks... \n");
    nf_register_hook(&ipv6_inhook);
#endif
#endif

#ifdef AUTO_UPDATE_PPPOE_INFO
    register_inetaddr_notifier(&qcaswitch_ip_notifier);
#endif // ifdef AUTO_UPDATE_PPPOE_INFO

    nat_wan_port = portbmp;

    /* Enable ACLs to handle MLD packets */
    upnp_ssdp_add_acl_rules();
    ipv6_snooping_solicted_node_add_acl_rules();
    ipv6_snooping_sextuple0_group_add_acl_rules();
    ipv6_snooping_quintruple0_1_group_add_acl_rules();

    napt_helper_hsl_init();
}

void host_helper_exit(void)
{
    napt_procfs_exit();

    unregister_netevent_notifier(&hnat_netevent_notifier);
#if 0
#ifdef CONFIG_IPV6_HWACCEL
    nf_unregister_hook(&ipv6_inhook);
#endif
#endif

	#ifdef AUTO_UPDATE_PPPOE_INFO
	unregister_inetaddr_notifier(&qcaswitch_ip_notifier);
	#endif
}

