/*
 * Copyright (c) 2010 Kip Macy. All rights reserved.
 * Copyright (C) 2017 THL A29 Limited, a Tencent company.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Derived in part from libplebnet's pn_veth.c.
 *
 */

#include <sys/ctype.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/kthread.h>
#include <sys/sched.h>
#include <sys/sockio.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <net/if_tap.h>
#include <net/if_dl.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet6/nd6.h>

#include <machine/atomic.h>

#include "ff_veth.h"
#include "ff_config.h"
#include "ff_dpdk_if.h"

struct ff_veth_softc {
    struct ifnet *ifp;
    uint8_t mac[ETHER_ADDR_LEN];
    char host_ifname[IF_NAMESIZE];

    in_addr_t ip;
    in_addr_t netmask;
    in_addr_t broadcast;
    in_addr_t gateway;

#ifdef INET6
    struct in6_addr ip6;
    struct in6_addr gateway6;
    uint8_t prefix_length;
#endif /* INET6 */

    struct ff_dpdk_if_context *host_ctx;
};

static int
ff_veth_config(struct ff_veth_softc *sc, struct ff_port_cfg *cfg)
{
    memcpy(sc->mac, cfg->mac, ETHER_ADDR_LEN);
    inet_pton(AF_INET, cfg->addr, &sc->ip);
    inet_pton(AF_INET, cfg->netmask, &sc->netmask);
    inet_pton(AF_INET, cfg->broadcast, &sc->broadcast);
    inet_pton(AF_INET, cfg->gateway, &sc->gateway);

#ifdef INET6
    if (cfg->addr6_str) {
        inet_pton(AF_INET6_LINUX, cfg->addr6_str, &sc->ip6);
        printf("%s: Addr6: %s\n", sc->host_ifname, cfg->addr6_str);

        if (cfg->gateway6_str) {
            inet_pton(AF_INET6_LINUX, cfg->gateway6_str, &sc->gateway6);
            printf("%s: Gateway6: %s\n", sc->host_ifname, cfg->gateway6_str);
        } else {
            printf("%s: No gateway6 config found.\n", sc->host_ifname);
        }

        sc->prefix_length = cfg->prefix_len == 0 ? 64 : cfg->prefix_len;
    } else {
        printf("%s: No addr6 config found.\n", sc->host_ifname);
    }
#endif /* INET6 */

    return 0;
}

static void
ff_veth_init(void *arg)
{
    struct ff_veth_softc *sc = arg;
    struct ifnet *ifp = sc->ifp;

    ifp->if_drv_flags |= IFF_DRV_RUNNING;
    ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
}

static void
ff_veth_start(struct ifnet *ifp)
{
    /* nothing to do */
}

static void
ff_veth_stop(struct ff_veth_softc *sc)
{
    struct ifnet *ifp = sc->ifp;

    ifp->if_drv_flags &= ~(IFF_DRV_RUNNING|IFF_DRV_OACTIVE);
}

static int
ff_veth_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
    int error = 0;
    struct ff_veth_softc *sc = ifp->if_softc;

    switch (cmd) {
    case SIOCSIFFLAGS:
        if (ifp->if_flags & IFF_UP) {
            ff_veth_init(sc);
        } else if (ifp->if_drv_flags & IFF_DRV_RUNNING)
            ff_veth_stop(sc);
        break;
    default:
        error = ether_ioctl(ifp, cmd, data);
        break;
    }

    return (error);
}

int
ff_mbuf_copydata(void *m, void *data, int off, int len)
{
    int ret;
    struct mbuf *mb = (struct mbuf *)m;

    if (off + len > mb->m_pkthdr.len) {
        return -1;
    }

    m_copydata(mb, off, len, data);

    return 0;
}

void
ff_mbuf_tx_offload(void *m, struct ff_tx_offload *offload)
{
    struct mbuf *mb = (struct mbuf *)m;
    if (mb->m_pkthdr.csum_flags & CSUM_IP) {
        offload->ip_csum = 1;
    }

    if (mb->m_pkthdr.csum_flags & CSUM_TCP) {
        offload->tcp_csum = 1;
    }

    if (mb->m_pkthdr.csum_flags & CSUM_UDP) {
        offload->udp_csum = 1;
    }

    if (mb->m_pkthdr.csum_flags & CSUM_SCTP) {
        offload->sctp_csum = 1;
    }

    if (mb->m_pkthdr.csum_flags & CSUM_TSO) {
        offload->tso_seg_size = mb->m_pkthdr.tso_segsz;
    }
}

void
ff_mbuf_free(void *m)
{
    m_freem((struct mbuf *)m);
}

static void
ff_mbuf_ext_free(struct mbuf *m, void *arg1, void *arg2)
{
    ff_dpdk_pktmbuf_free(arg1);
}

void *
ff_mbuf_gethdr(void *pkt, uint16_t total, void *data,
    uint16_t len, uint8_t rx_csum)
{
    struct mbuf *m = m_gethdr(M_NOWAIT, MT_DATA);
    if (m == NULL) {
        return NULL;
    }

    if (m_pkthdr_init(m, M_NOWAIT) != 0) {
        return NULL;
    }

    m_extadd(m, data, len, ff_mbuf_ext_free, pkt, NULL, 0, EXT_DISPOSABLE);

    m->m_pkthdr.len = total;
    m->m_len = len;
    m->m_next = NULL;
    m->m_nextpkt = NULL;

    if (rx_csum) {
        m->m_pkthdr.csum_flags = CSUM_IP_CHECKED | CSUM_IP_VALID |
            CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
        m->m_pkthdr.csum_data = 0xffff;
    }
    return (void *)m;
}

void *
ff_mbuf_get(void *p, void *m, void *data, uint16_t len)
{
    struct mbuf *prev = (struct mbuf *)p;
    struct mbuf *mb = m_get(M_NOWAIT, MT_DATA);

    if (mb == NULL) {
        return NULL; 
    }

    m_extadd(mb, data, len, ff_mbuf_ext_free, m, NULL, 0, EXT_DISPOSABLE);

    mb->m_next = NULL;
    mb->m_nextpkt = NULL;
    mb->m_len = len;

    if (prev != NULL) {
        prev->m_next = mb;
    }

    return (void *)mb;
}

void
ff_veth_process_packet(void *arg, void *m)
{
    struct ifnet *ifp = (struct ifnet *)arg;
    struct mbuf *mb = (struct mbuf *)m;

    mb->m_pkthdr.rcvif = ifp;

    ifp->if_input(ifp, mb);
}

static int
ff_veth_transmit(struct ifnet *ifp, struct mbuf *m)
{
    struct ff_veth_softc *sc = (struct ff_veth_softc *)ifp->if_softc;
    return ff_dpdk_if_send(sc->host_ctx, (void*)m, m->m_pkthdr.len);
}

static void
ff_veth_qflush(struct ifnet *ifp)
{

}

static int
ff_veth_setaddr(struct ff_veth_softc *sc)
{
    struct in_aliasreq req;
    bzero(&req, sizeof req);
    strcpy(req.ifra_name, sc->ifp->if_dname);

    struct sockaddr_in sa;
    bzero(&sa, sizeof(sa));
    sa.sin_len = sizeof(sa);
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = sc->ip;
    bcopy(&sa, &req.ifra_addr, sizeof(sa));

    sa.sin_addr.s_addr = sc->netmask;
    bcopy(&sa, &req.ifra_mask, sizeof(sa));

    sa.sin_addr.s_addr = sc->broadcast;
    bcopy(&sa, &req.ifra_broadaddr, sizeof(sa));

    struct socket *so = NULL;
    socreate(AF_INET, &so, SOCK_DGRAM, 0, curthread->td_ucred, curthread);
    int ret = ifioctl(so, SIOCAIFADDR, (caddr_t)&req, curthread);

    sofree(so);

    return ret;
}

static int
ff_veth_set_gateway(struct ff_veth_softc *sc)
{
    struct sockaddr_in gw;
    bzero(&gw, sizeof(gw));
    gw.sin_len = sizeof(gw);
    gw.sin_family = AF_INET;
    gw.sin_addr.s_addr = sc->gateway;

    struct sockaddr_in dst;
    bzero(&dst, sizeof(dst));
    dst.sin_len = sizeof(dst);
    dst.sin_family = AF_INET;
    dst.sin_addr.s_addr = 0;

    struct sockaddr_in nm;
    bzero(&nm, sizeof(nm));
    nm.sin_len = sizeof(nm);
    nm.sin_family = AF_INET;
    nm.sin_addr.s_addr = 0;

    return rtrequest_fib(RTM_ADD, (struct sockaddr *)&dst, (struct sockaddr *)&gw,
        (struct sockaddr *)&nm, RTF_GATEWAY, NULL, RT_DEFAULT_FIB);
}

#ifdef INET6
static int
ff_veth_setaddr6(struct ff_veth_softc *sc)
{
    struct in6_aliasreq ifr6;
    bzero(&ifr6, sizeof(ifr6));
    strcpy(ifr6.ifra_name, sc->ifp->if_dname);

    ifr6.ifra_addr.sin6_len = sizeof ifr6.ifra_addr;
    ifr6.ifra_addr.sin6_family = AF_INET6;
    ifr6.ifra_addr.sin6_addr = sc->ip6;

    ifr6.ifra_prefixmask.sin6_len = sizeof ifr6.ifra_prefixmask;
    memset(&ifr6.ifra_prefixmask.sin6_addr, 0xff, sc->prefix_length / 8);
    uint8_t mask_size_mod = sc->prefix_length % 8;
    if (mask_size_mod)
    {
        ifr6.ifra_prefixmask.sin6_addr.__u6_addr.__u6_addr8[sc->prefix_length / 8] = ((1 << mask_size_mod) - 1) << (8 - mask_size_mod);
    }

    ifr6.ifra_lifetime.ia6t_pltime = ifr6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;

    struct socket *so = NULL;
    socreate(AF_INET6, &so, SOCK_DGRAM, 0, curthread->td_ucred, curthread);
    int ret = ifioctl(so, SIOCAIFADDR_IN6, (caddr_t)&ifr6, curthread);

    sofree(so);

    return ret;
}

static int
ff_veth_set_gateway6(struct ff_veth_softc *sc)
{
    struct sockaddr_in6 gw, dst, nm;

    bzero(&gw, sizeof(gw));
    bzero(&dst, sizeof(dst));
    bzero(&nm, sizeof(nm));

    gw.sin6_len = dst.sin6_len = nm.sin6_len = sizeof(struct sockaddr_in6);
    gw.sin6_family = dst.sin6_family = AF_INET6;

    gw.sin6_addr = sc->gateway6;
    //dst.sin6_addr = nm.sin6_addr = 0;

    return rtrequest_fib(RTM_ADD, (struct sockaddr *)&dst, (struct sockaddr *)&gw,
        (struct sockaddr *)&nm, RTF_GATEWAY, NULL, RT_DEFAULT_FIB);
}
#endif /* INET6 */

static int
ff_veth_setup_interface(struct ff_veth_softc *sc, struct ff_port_cfg *cfg)
{
    struct ifnet *ifp;

    ifp = sc->ifp = if_alloc(IFT_ETHER);

    ifp->if_init = ff_veth_init;
    ifp->if_softc = sc;

    if_initname(ifp, sc->host_ifname, IF_DUNIT_NONE);
    ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
    ifp->if_ioctl = ff_veth_ioctl;
    ifp->if_start = ff_veth_start;
    ifp->if_transmit = ff_veth_transmit;
    ifp->if_qflush = ff_veth_qflush;
    ether_ifattach(ifp, sc->mac);

    if (cfg->hw_features.rx_csum) {
        ifp->if_capabilities |= IFCAP_RXCSUM;
    }
    if (cfg->hw_features.tx_csum_ip) {
        ifp->if_capabilities |= IFCAP_TXCSUM;
        ifp->if_hwassist |= CSUM_IP;
    }
    if (cfg->hw_features.tx_csum_l4) {
        ifp->if_hwassist |= CSUM_DELAY_DATA;
    }
    if (cfg->hw_features.tx_tso) {
        ifp->if_capabilities |= IFCAP_TSO;
        ifp->if_hwassist |= CSUM_TSO;
    }

    ifp->if_capenable = ifp->if_capabilities;

    sc->host_ctx = ff_dpdk_register_if((void *)sc, (void *)sc->ifp, cfg);
    if (sc->host_ctx == NULL) {
        printf("%s: Failed to register dpdk interface\n", sc->host_ifname);
        return -1;
    }

    //set ip
    int ret = ff_veth_setaddr(sc);
    if (ret != 0) {
        printf("ff_veth_setaddr failed\n");
    }
    ret = ff_veth_set_gateway(sc);
    if (ret != 0) {
        printf("ff_veth_set_gateway failed\n");
    }

#ifdef INET6
    // Set IPv6
    if (cfg->addr6_str) {
        ret = ff_veth_setaddr6(sc);
        if (ret != 0) {
            printf("ff_veth_setaddr6 failed\n");
        }

        if (cfg->gateway6_str) {
            ret = ff_veth_set_gateway6(sc);
            if (ret != 0) {
                printf("ff_veth_set_gateway6 failed\n");
            }
        }
    }
#endif /* INET6 */

    return (0);
}

void *
ff_veth_attach(struct ff_port_cfg *cfg)
{
    struct ff_veth_softc *sc = NULL;
    int error;

    sc = malloc(sizeof(struct ff_veth_softc), M_DEVBUF, M_WAITOK);
    if (NULL == sc) {
        printf("ff_veth_softc allocation failed\n");
        goto fail;
    }
    memset(sc, 0, sizeof(struct ff_veth_softc));

    snprintf(sc->host_ifname, sizeof(sc->host_ifname), ff_IF_NAME, cfg->port_id);

    error = ff_veth_config(sc, cfg);
    if (0 != error) {
        goto fail;
    }

    if (0 != ff_veth_setup_interface(sc, cfg)) {
        goto fail;
    }

    return sc->host_ctx;

fail:
    if (sc) {
        if (sc->host_ctx)
            ff_dpdk_deregister_if(sc->host_ctx);

        free(sc, M_DEVBUF);
    }

    return NULL;
}

int
ff_veth_detach(void *arg)
{
    struct ff_veth_softc *sc = (struct ff_veth_softc *)arg;
    if (sc) {
        ff_dpdk_deregister_if(sc->host_ctx);
        free(sc, M_DEVBUF);
    }

    return (0);
}

void *
ff_veth_softc_to_hostc(void *softc)
{
    struct ff_veth_softc *sc = (struct ff_veth_softc *)softc;
    return (void *)sc->host_ctx;
}

/********************
*  get next mbuf's addr, current mbuf's data and datalen.
*  
********************/
int ff_next_mbuf(void **mbuf_bsd, void **data, unsigned *len)
{
    struct mbuf *mb = *(struct mbuf **)mbuf_bsd;

    *len = mb->m_len;
    *data = mb->m_data;

    if (mb->m_next)
        *mbuf_bsd = mb->m_next;
    else
        *mbuf_bsd = NULL;
    return 0;
}

void * ff_mbuf_mtod(void* bsd_mbuf)
{
    if ( !bsd_mbuf )
        return NULL;
    return (void*)((struct mbuf *)bsd_mbuf)->m_data;
}

// get source rte_mbuf from ext cluster, which carry rte_mbuf while recving pkt, such as arp.
void* ff_rte_frm_extcl(void* mbuf)
{
    struct mbuf *bsd_mbuf = mbuf;

    if ( (bsd_mbuf->m_flags & M_EXT) &&
        bsd_mbuf->m_ext.ext_type == EXT_DISPOSABLE && bsd_mbuf->m_ext.ext_free == ff_mbuf_ext_free ) {
        return bsd_mbuf->m_ext.ext_arg1;
    }
    else 
        return NULL;
}

void
ff_mbuf_set_vlan_info(void *hdr, uint16_t vlan_tci) {
    struct mbuf *m = (struct mbuf *)hdr;
    m->m_pkthdr.ether_vtag = vlan_tci;
    m->m_flags |= M_VLANTAG;
    return;
}