/* $Id: NATEngineImpl.cpp $ */
/** @file
 * Implementation of INATEngine in VBoxSVC.
 */

/*
 * Copyright (C) 2010-2015 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.
 */

#include "NATEngineImpl.h"
#include "AutoCaller.h"
#include "Logging.h"
#include "MachineImpl.h"
#include "GuestOSTypeImpl.h"

#include <iprt/string.h>
#include <iprt/cpp/utils.h>

#include <VBox/err.h>
#include <VBox/settings.h>
#include <VBox/com/array.h>

typedef std::map<Utf8Str, settings::NATRule> NATRuleMap;

struct NATEngineData
{
    NATEngineData() : mMtu(0),
             mSockRcv(0),
             mSockSnd(0),
             mTcpRcv(0),
             mTcpSnd(0),
             mDNSPassDomain(TRUE),
             mDNSProxy(FALSE),
             mDNSUseHostResolver(FALSE),
             mAliasMode(0)
    {}

    com::Utf8Str mNetwork;
    com::Utf8Str mBindIP;
    uint32_t mMtu;
    uint32_t mSockRcv;
    uint32_t mSockSnd;
    uint32_t mTcpRcv;
    uint32_t mTcpSnd;
    /* TFTP service */
    Utf8Str mTFTPPrefix;
    Utf8Str mTFTPBootFile;
    Utf8Str mTFTPNextServer;
    /* DNS service */
    BOOL mDNSPassDomain;
    BOOL mDNSProxy;
    BOOL mDNSUseHostResolver;
    /* Alias service */
    ULONG mAliasMode;
    /* Port forwarding rules */
    NATRuleMap mNATRules;
};

struct NATEngine::Data
{
    Backupable<NATEngineData> m;
};


// constructor / destructor
////////////////////////////////////////////////////////////////////////////////

NATEngine::NATEngine():mData(NULL), mParent(NULL), mAdapter(NULL) {}
NATEngine::~NATEngine(){}

HRESULT NATEngine::FinalConstruct()
{
    return BaseFinalConstruct();
}

void NATEngine::FinalRelease()
{
    uninit();
    BaseFinalRelease();
}


HRESULT NATEngine::init(Machine *aParent, INetworkAdapter *aAdapter)
{
    AutoInitSpan autoInitSpan(this);
    AssertReturn(autoInitSpan.isOk(), E_FAIL);
    autoInitSpan.setSucceeded();
    mData = new Data();
    mData->m.allocate();
    mData->m->mNetwork.setNull();
    mData->m->mBindIP.setNull();
    unconst(mParent) = aParent;
    unconst(mAdapter) = aAdapter;
    return S_OK;
}

HRESULT NATEngine::init(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat)
{
    AutoInitSpan autoInitSpan(this);
    AssertReturn(autoInitSpan.isOk(), E_FAIL);
    Log(("init that:%p this:%p\n", aThat, this));

    AutoCaller thatCaller(aThat);
    AssertComRCReturnRC(thatCaller.rc());

    AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);

    mData = new Data();
    mData->m.share(aThat->mData->m);
    unconst(mParent) = aParent;
    unconst(mAdapter) = aAdapter;
    unconst(mPeer) = aThat;
    autoInitSpan.setSucceeded();
    return S_OK;
}

HRESULT NATEngine::initCopy(Machine *aParent, INetworkAdapter *aAdapter, NATEngine *aThat)
{
    AutoInitSpan autoInitSpan(this);
    AssertReturn(autoInitSpan.isOk(), E_FAIL);

    Log(("initCopy that:%p this:%p\n", aThat, this));

    AutoCaller thatCaller(aThat);
    AssertComRCReturnRC(thatCaller.rc());

    AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS);

    mData = new Data();
    mData->m.attachCopy(aThat->mData->m);
    unconst(mAdapter) = aAdapter;
    unconst(mParent) = aParent;
    autoInitSpan.setSucceeded();
    return BaseFinalConstruct();
}


void NATEngine::uninit()
{
    AutoUninitSpan autoUninitSpan(this);
    if (autoUninitSpan.uninitDone())
        return;

    mData->m.free();
    delete mData;
    mData = NULL;
    unconst(mPeer) = NULL;
    unconst(mParent) = NULL;
}

bool NATEngine::i_isModified()
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    bool fModified = mData->m.isBackedUp();
    return fModified;
}

void NATEngine::i_rollback()
{
    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    mData->m.rollback();
}

void NATEngine::i_commit()
{
    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    /* sanity too */
    AutoCaller peerCaller(mPeer);
    AssertComRCReturnVoid(peerCaller.rc());

    /* lock both for writing since we modify both (mPeer is "master" so locked
     * first) */
    AutoMultiWriteLock2 alock(mPeer, this COMMA_LOCKVAL_SRC_POS);
    if (mData->m.isBackedUp())
    {
        mData->m.commit();
        if (mPeer)
            mPeer->mData->m.attach(mData->m);
    }
}

void NATEngine::i_copyFrom(NATEngine *aThat)
{
    AssertReturnVoid(aThat != NULL);

    /* sanity */
    AutoCaller autoCaller(this);
    AssertComRCReturnVoid(autoCaller.rc());

    /* sanity too */
    AutoCaller thatCaller(aThat);
    AssertComRCReturnVoid(thatCaller.rc());

    /* peer is not modified, lock it for reading (aThat is "master" so locked
     * first) */
    AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS);
    AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS);

    /* this will back up current data */
    mData->m.assignCopy(aThat->mData->m);
}

HRESULT NATEngine::getNetworkSettings(ULONG *aMtu, ULONG *aSockSnd, ULONG *aSockRcv, ULONG *aTcpWndSnd, ULONG *aTcpWndRcv)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (aMtu)
        *aMtu = mData->m->mMtu;
    if (aSockSnd)
        *aSockSnd = mData->m->mSockSnd;
    if (aSockRcv)
        *aSockRcv = mData->m->mSockRcv;
    if (aTcpWndSnd)
        *aTcpWndSnd = mData->m->mTcpSnd;
    if (aTcpWndRcv)
        *aTcpWndRcv = mData->m->mTcpRcv;

    return S_OK;
}

HRESULT NATEngine::setNetworkSettings(ULONG aMtu, ULONG aSockSnd, ULONG aSockRcv, ULONG aTcpWndSnd, ULONG aTcpWndRcv)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (   aMtu || aSockSnd || aSockRcv
        || aTcpWndSnd || aTcpWndRcv)
    {
        mData->m.backup();
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    if (aMtu)
        mData->m->mMtu = aMtu;
    if (aSockSnd)
        mData->m->mSockSnd = aSockSnd;
    if (aSockRcv)
        mData->m->mSockRcv = aSockSnd;
    if (aTcpWndSnd)
        mData->m->mTcpSnd = aTcpWndSnd;
    if (aTcpWndRcv)
        mData->m->mTcpRcv = aTcpWndRcv;

    return S_OK;
}


HRESULT NATEngine::getRedirects(std::vector<com::Utf8Str> &aRedirects)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    aRedirects.resize(mData->m->mNATRules.size());
    size_t i = 0;
    NATRuleMap::const_iterator it;
    for (it = mData->m->mNATRules.begin(); it != mData->m->mNATRules.end(); ++it, ++i)
    {
        settings::NATRule r = it->second;
        aRedirects[i] = Utf8StrFmt("%s,%d,%s,%d,%s,%d",
                                   r.strName.c_str(),
                                   r.proto,
                                   r.strHostIP.c_str(),
                                   r.u16HostPort,
                                   r.strGuestIP.c_str(),
                                   r.u16GuestPort);
    }
    return S_OK;
}

HRESULT NATEngine::addRedirect(const com::Utf8Str &aName, NATProtocol_T aProto, const com::Utf8Str &aHostIP,
                               USHORT aHostPort, const com::Utf8Str &aGuestIP, USHORT aGuestPort)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    Utf8Str name = aName;
    settings::NATRule r;
    const char *proto;
    switch (aProto)
    {
        case NATProtocol_TCP:
            proto = "tcp";
            break;
        case NATProtocol_UDP:
            proto = "udp";
            break;
        default:
            return E_INVALIDARG;
    }
    if (name.isEmpty())
        name = Utf8StrFmt("%s_%d_%d", proto, aHostPort, aGuestPort);

    NATRuleMap::iterator it;
    for (it = mData->m->mNATRules.begin(); it != mData->m->mNATRules.end(); ++it)
    {
        r = it->second;
        if (it->first == name)
            return setError(E_INVALIDARG,
                            tr("A NAT rule of this name already exists"));
        if (   r.strHostIP == aHostIP
            && r.u16HostPort == aHostPort
            && r.proto == aProto)
            return setError(E_INVALIDARG,
                            tr("A NAT rule for this host port and this host IP already exists"));
    }

    mData->m.backup();
    r.strName = name.c_str();
    r.proto = aProto;
    r.strHostIP = aHostIP;
    r.u16HostPort = aHostPort;
    r.strGuestIP = aGuestIP;
    r.u16GuestPort = aGuestPort;
    mData->m->mNATRules.insert(std::make_pair(name, r));
    mParent->i_setModified(Machine::IsModified_NetworkAdapters);

    ULONG ulSlot;
    mAdapter->COMGETTER(Slot)(&ulSlot);

    alock.release();
    mParent->i_onNATRedirectRuleChange(ulSlot, FALSE, Bstr(name).raw(), aProto, Bstr(r.strHostIP).raw(),
                                       r.u16HostPort, Bstr(r.strGuestIP).raw(), r.u16GuestPort);
    return S_OK;
}

HRESULT NATEngine::removeRedirect(const com::Utf8Str &aName)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    NATRuleMap::iterator it = mData->m->mNATRules.find(aName);
    if (it == mData->m->mNATRules.end())
        return E_INVALIDARG;
    mData->m.backup();
    /*
     * NB: "it" may now point to the backup!  In that case it's ok to
     * get data from the backup copy of mNATRules via it, but we can't
     * erase(it) from potentially new mNATRules.
     */
    settings::NATRule r = it->second;
    Utf8Str strHostIP = r.strHostIP;
    Utf8Str strGuestIP = r.strGuestIP;
    NATProtocol_T proto = r.proto;
    uint16_t u16HostPort = r.u16HostPort;
    uint16_t u16GuestPort = r.u16GuestPort;
    ULONG ulSlot;
    mAdapter->COMGETTER(Slot)(&ulSlot);

    mData->m->mNATRules.erase(aName); /* NB: erase by key, "it" may not be valid */
    mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    alock.release();
    mParent->i_onNATRedirectRuleChange(ulSlot, TRUE, Bstr(aName).raw(), proto, Bstr(strHostIP).raw(),
                                       u16HostPort, Bstr(strGuestIP).raw(), u16GuestPort);
    return S_OK;
}

HRESULT NATEngine::i_loadSettings(const settings::NAT &data)
{
    AutoCaller autoCaller(this);
    AssertComRCReturnRC(autoCaller.rc());

    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    HRESULT rc = S_OK;
    mData->m->mNetwork = data.strNetwork;
    mData->m->mBindIP = data.strBindIP;
    mData->m->mMtu = data.u32Mtu;
    mData->m->mSockSnd = data.u32SockSnd;
    mData->m->mTcpRcv = data.u32TcpRcv;
    mData->m->mTcpSnd = data.u32TcpSnd;
    /* TFTP */
    mData->m->mTFTPPrefix = data.strTFTPPrefix;
    mData->m->mTFTPBootFile = data.strTFTPBootFile;
    mData->m->mTFTPNextServer = data.strTFTPNextServer;
    /* DNS */
    mData->m->mDNSPassDomain = data.fDNSPassDomain;
    mData->m->mDNSProxy = data.fDNSProxy;
    mData->m->mDNSUseHostResolver = data.fDNSUseHostResolver;
    /* Alias */
    mData->m->mAliasMode  = (data.fAliasUseSamePorts ? NATAliasMode_AliasUseSamePorts : 0);
    mData->m->mAliasMode |= (data.fAliasLog          ? NATAliasMode_AliasLog          : 0);
    mData->m->mAliasMode |= (data.fAliasProxyOnly    ? NATAliasMode_AliasProxyOnly    : 0);
    /* port forwarding */
    mData->m->mNATRules.clear();
    for (settings::NATRuleList::const_iterator it = data.llRules.begin();
        it != data.llRules.end(); ++it)
    {
        mData->m->mNATRules.insert(std::make_pair(it->strName, *it));
    }
    return rc;
}


HRESULT NATEngine::i_saveSettings(settings::NAT &data)
{
    AutoCaller autoCaller(this);
    AssertComRCReturnRC(autoCaller.rc());

    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    HRESULT rc = S_OK;
    data.strNetwork = mData->m->mNetwork;
    data.strBindIP = mData->m->mBindIP;
    data.u32Mtu = mData->m->mMtu;
    data.u32SockRcv = mData->m->mSockRcv;
    data.u32SockSnd = mData->m->mSockSnd;
    data.u32TcpRcv = mData->m->mTcpRcv;
    data.u32TcpSnd = mData->m->mTcpSnd;
    /* TFTP */
    data.strTFTPPrefix = mData->m->mTFTPPrefix;
    data.strTFTPBootFile = mData->m->mTFTPBootFile;
    data.strTFTPNextServer = mData->m->mTFTPNextServer;
    /* DNS */
    data.fDNSPassDomain = !!mData->m->mDNSPassDomain;
    data.fDNSProxy = !!mData->m->mDNSProxy;
    data.fDNSUseHostResolver = !!mData->m->mDNSUseHostResolver;
    /* Alias */
    data.fAliasLog = !!(mData->m->mAliasMode & NATAliasMode_AliasLog);
    data.fAliasProxyOnly = !!(mData->m->mAliasMode & NATAliasMode_AliasProxyOnly);
    data.fAliasUseSamePorts = !!(mData->m->mAliasMode & NATAliasMode_AliasUseSamePorts);

    for (NATRuleMap::iterator it = mData->m->mNATRules.begin();
        it != mData->m->mNATRules.end(); ++it)
        data.llRules.push_back(it->second);
    return rc;
}

HRESULT NATEngine::setNetwork(const com::Utf8Str &aNetwork)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (Bstr(mData->m->mNetwork) != aNetwork)
    {
        mData->m.backup();
        mData->m->mNetwork = aNetwork;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}


HRESULT NATEngine::getNetwork(com::Utf8Str &aNetwork)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (!mData->m->mNetwork.isEmpty())
    {
        aNetwork = mData->m->mNetwork;
        Log(("Getter (this:%p) Network: %s\n", this, mData->m->mNetwork.c_str()));
    }
    return S_OK;
}

HRESULT NATEngine::setHostIP(const com::Utf8Str &aHostIP)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (Bstr(mData->m->mBindIP) != aHostIP)
    {
        mData->m.backup();
        mData->m->mBindIP = aHostIP;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::getHostIP(com::Utf8Str &aBindIP)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (!mData->m->mBindIP.isEmpty())
        aBindIP = mData->m->mBindIP;
    return S_OK;
}

HRESULT NATEngine::setTFTPPrefix(const com::Utf8Str &aTFTPPrefix)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (Bstr(mData->m->mTFTPPrefix) != aTFTPPrefix)
    {
        mData->m.backup();
        mData->m->mTFTPPrefix = aTFTPPrefix;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}


HRESULT NATEngine::getTFTPPrefix(com::Utf8Str &aTFTPPrefix)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (!mData->m->mTFTPPrefix.isEmpty())
    {
        aTFTPPrefix = mData->m->mTFTPPrefix;
        Log(("Getter (this:%p) TFTPPrefix: %s\n", this, mData->m->mTFTPPrefix.c_str()));
    }
    return S_OK;
}

HRESULT NATEngine::setTFTPBootFile(const com::Utf8Str &aTFTPBootFile)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (Bstr(mData->m->mTFTPBootFile) != aTFTPBootFile)
    {
        mData->m.backup();
        mData->m->mTFTPBootFile = aTFTPBootFile;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}


HRESULT NATEngine::getTFTPBootFile(com::Utf8Str &aTFTPBootFile)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (!mData->m->mTFTPBootFile.isEmpty())
    {
        aTFTPBootFile = mData->m->mTFTPBootFile;
        Log(("Getter (this:%p) BootFile: %s\n", this, mData->m->mTFTPBootFile.c_str()));
    }
    return S_OK;
}


HRESULT NATEngine::setTFTPNextServer(const com::Utf8Str &aTFTPNextServer)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (Bstr(mData->m->mTFTPNextServer) != aTFTPNextServer)
    {
        mData->m.backup();
        mData->m->mTFTPNextServer = aTFTPNextServer;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::getTFTPNextServer(com::Utf8Str &aTFTPNextServer)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    if (!mData->m->mTFTPNextServer.isEmpty())
    {
        aTFTPNextServer =  mData->m->mTFTPNextServer;
        Log(("Getter (this:%p) NextServer: %s\n", this, mData->m->mTFTPNextServer.c_str()));
    }
    return S_OK;
}

/* DNS */
HRESULT NATEngine::setDNSPassDomain(BOOL aDNSPassDomain)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (mData->m->mDNSPassDomain != aDNSPassDomain)
    {
        mData->m.backup();
        mData->m->mDNSPassDomain = aDNSPassDomain;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::getDNSPassDomain(BOOL *aDNSPassDomain)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    *aDNSPassDomain = mData->m->mDNSPassDomain;
    return S_OK;
}


HRESULT NATEngine::setDNSProxy(BOOL aDNSProxy)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (mData->m->mDNSProxy != aDNSProxy)
    {
        mData->m.backup();
        mData->m->mDNSProxy = aDNSProxy;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::getDNSProxy(BOOL *aDNSProxy)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
    *aDNSProxy = mData->m->mDNSProxy;
    return S_OK;
}


HRESULT NATEngine::getDNSUseHostResolver(BOOL *aDNSUseHostResolver)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    *aDNSUseHostResolver = mData->m->mDNSUseHostResolver;
    return S_OK;
}


HRESULT NATEngine::setDNSUseHostResolver(BOOL aDNSUseHostResolver)
{
    if (mData->m->mDNSUseHostResolver != aDNSUseHostResolver)
    {
        mData->m.backup();
        mData->m->mDNSUseHostResolver = aDNSUseHostResolver;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::setAliasMode(ULONG aAliasMode)
{
    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);

    if (mData->m->mAliasMode != aAliasMode)
    {
        mData->m.backup();
        mData->m->mAliasMode = aAliasMode;
        mParent->i_setModified(Machine::IsModified_NetworkAdapters);
    }
    return S_OK;
}

HRESULT NATEngine::getAliasMode(ULONG *aAliasMode)
{
    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
    *aAliasMode = mData->m->mAliasMode;
    return S_OK;
}

