/* $Id: VBoxNetARP.cpp $ */
/** @file
 * VBoxNetARP - IntNet ARP Client Routines.
 */

/*
 * Copyright (C) 2009-2010 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEFAULT
#include "VBoxNetLib.h"
#include <iprt/string.h>
#include <VBox/intnetinline.h>
#include <VBox/log.h>


/**
 * Deal with ARP queries.
 *
 * @returns true if ARP.
 *
 * @param   pSession        The support driver session.
 * @param   hIf             The internal network interface handle.
 * @param   pBuf            The internal network interface buffer.
 * @param   pMacAddr        Our MAC address.
 * @param   IPv4Addr        Our IPv4 address.
 */
bool VBoxNetArpHandleIt(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, PCRTMAC pMacAddr, RTNETADDRIPV4 IPv4Addr)
{
    /*
     * Valid IntNet Ethernet frame? Skip GSO, no ARP in there.
     */
    PCINTNETHDR pHdr = IntNetRingGetNextFrameToRead(&pBuf->Recv);
    if (   !pHdr
        || pHdr->u8Type != INTNETHDR_TYPE_FRAME)
        return false;

    size_t          cbFrame = pHdr->cbFrame;
    const void     *pvFrame = IntNetHdrGetFramePtr(pHdr, pBuf);
    PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame;

    /*
     * Arp frame?
     */
    if (pEthHdr->EtherType != RT_H2N_U16_C(RTNET_ETHERTYPE_ARP))
        return false;
    if (   (   pEthHdr->DstMac.au16[0] != 0xffff
            || pEthHdr->DstMac.au16[1] != 0xffff
            || pEthHdr->DstMac.au16[2] != 0xffff)
        && (   pEthHdr->DstMac.au16[0] != pMacAddr->au16[0]
            || pEthHdr->DstMac.au16[1] != pMacAddr->au16[1]
            || pEthHdr->DstMac.au16[2] != pMacAddr->au16[2])
       )
        return false;
    if (cbFrame < sizeof(RTNETARPIPV4) + sizeof(RTNETETHERHDR))
        return false;

    PCRTNETARPHDR pArpHdr = (PCRTNETARPHDR)(pEthHdr + 1);
    if (pArpHdr->ar_htype != RT_H2N_U16_C(RTNET_ARP_ETHER))
        return false;
    if (pArpHdr->ar_hlen != sizeof(RTMAC))
        return false;
    if (pArpHdr->ar_ptype != RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4))
        return false;
    if (pArpHdr->ar_plen != sizeof(RTNETADDRIPV4))
        return false;

    /* It's ARP, alright. Anything we need to do something about. */
    PCRTNETARPIPV4 pArp = (PCRTNETARPIPV4)pArpHdr;
    switch (pArp->Hdr.ar_oper)
    {
        case RT_H2N_U16_C(RTNET_ARPOP_REQUEST):
        case RT_H2N_U16_C(RTNET_ARPOP_REVREQUEST):
        case RT_H2N_U16_C(RTNET_ARPOP_INVREQUEST):
            break;
        default:
            return true;
    }

    /*
     * Deal with the queries.
     */
    RTNETARPIPV4 Reply;
    switch (pArp->Hdr.ar_oper)
    {
        /* 'Who has ar_tpa? Tell ar_spa.'  */
        case RT_H2N_U16_C(RTNET_ARPOP_REQUEST):
            if (pArp->ar_tpa.u != IPv4Addr.u)
                return true;
            Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_REPLY);
            break;

        case RT_H2N_U16_C(RTNET_ARPOP_REVREQUEST):
            if (    pArp->ar_tha.au16[0] != pMacAddr->au16[0]
                ||  pArp->ar_tha.au16[1] != pMacAddr->au16[1]
                ||  pArp->ar_tha.au16[2] != pMacAddr->au16[2])
                return true;
            Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_REVREPLY);
            break;

        case RT_H2N_U16_C(RTNET_ARPOP_INVREQUEST):
            /** @todo RTNET_ARPOP_INVREQUEST */
            return true;
            //Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_INVREPLY);
            //break;
    }

    /*
     * Complete the reply and send it.
     */
    Reply.Hdr.ar_htype = RT_H2N_U16_C(RTNET_ARP_ETHER);
    Reply.Hdr.ar_ptype = RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4);
    Reply.Hdr.ar_hlen  = sizeof(RTMAC);
    Reply.Hdr.ar_plen  = sizeof(RTNETADDRIPV4);
    Reply.ar_sha = *pMacAddr;
    Reply.ar_spa = IPv4Addr;
    Reply.ar_tha = pArp->ar_sha;
    Reply.ar_tpa = pArp->ar_spa;


    RTNETETHERHDR EthHdr;
    EthHdr.DstMac    = pArp->ar_sha;
    EthHdr.SrcMac    = *pMacAddr;
    EthHdr.EtherType = RT_H2N_U16_C(RTNET_ETHERTYPE_ARP);

    uint8_t abTrailer[60 - sizeof(Reply) - sizeof(EthHdr)];
    memset(abTrailer, '\0', sizeof(abTrailer));

    INTNETSEG aSegs[3];
    aSegs[0].cb = sizeof(EthHdr);
    aSegs[0].pv = &EthHdr;

    aSegs[1].pv = &Reply;
    aSegs[1].cb = sizeof(Reply);

    aSegs[2].pv = &abTrailer[0];
    aSegs[2].cb = sizeof(abTrailer);

    VBoxNetIntIfSend(pSession, hIf, pBuf, RT_ELEMENTS(aSegs), &aSegs[0], true /* fFlush */);

    return true;
}

