/* $Id: VBoxNetDHCP.cpp $ */
/** @file
 * VBoxNetDHCP - DHCP Service for connecting to IntNet.
 */

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

/** @page pg_net_dhcp       VBoxNetDHCP
 *
 * Write a few words...
 *
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <iprt/alloca.h>
#include <iprt/buildconfig.h>
#include <iprt/err.h>
#include <iprt/net.h>                   /* must come before getopt */
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/stream.h>
#include <iprt/time.h>
#include <iprt/string.h>

#include <VBox/sup.h>
#include <VBox/intnet.h>
#include <VBox/intnetinline.h>
#include <VBox/vmm/vmm.h>
#include <VBox/version.h>

#include "../NetLib/VBoxNetLib.h"

#include <vector>
#include <string>

#ifdef RT_OS_WINDOWS /* WinMain */
# include <Windows.h>
# include <stdlib.h>
#endif


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/

/**
 * DHCP configuration item.
 *
 * This is all public data because I'm too lazy to do it properly right now.
 */
class VBoxNetDhcpCfg
{
public:
    /** The etheret addresses this matches config applies to.
     * An empty vector means 'ANY'. */
    std::vector<RTMAC>          m_MacAddresses;
    /** The upper address in the range. */
    RTNETADDRIPV4               m_UpperAddr;
    /** The lower address in the range. */
    RTNETADDRIPV4               m_LowerAddr;

    /** Option 1: The net mask. */
    RTNETADDRIPV4               m_SubnetMask;
    /* * Option 2: The time offset. */
    /** Option 3: Routers for the subnet. */
    std::vector<RTNETADDRIPV4>  m_Routers;
    /* * Option 4: Time server. */
    /* * Option 5: Name server. */
    /** Option 6: Domain Name Server (DNS) */
    std::vector<RTNETADDRIPV4>  m_DNSes;
    /* * Option 7: Log server. */
    /* * Option 8: Cookie server. */
    /* * Option 9: LPR server. */
    /* * Option 10: Impress server. */
    /* * Option 11: Resource location server. */
    /* * Option 12: Host name. */
    std::string                 m_HostName;
    /* * Option 13: Boot file size option. */
    /* * Option 14: Merit dump file. */
    /** Option 15: Domain name. */
    std::string                 m_DomainName;
    /* * Option 16: Swap server. */
    /* * Option 17: Root path. */
    /* * Option 18: Extension path. */
    /* * Option 19: IP forwarding enable/disable. */
    /* * Option 20: Non-local routing enable/disable. */
    /* * Option 21: Policy filter. */
    /* * Option 22: Maximum datagram reassembly size (MRS). */
    /* * Option 23: Default IP time-to-live. */
    /* * Option 24: Path MTU aging timeout. */
    /* * Option 25: Path MTU plateau table. */
    /* * Option 26: Interface MTU. */
    /* * Option 27: All subnets are local. */
    /* * Option 28: Broadcast address. */
    /* * Option 29: Perform maximum discovery. */
    /* * Option 30: Mask supplier. */
    /* * Option 31: Perform route discovery. */
    /* * Option 32: Router solicitation address. */
    /* * Option 33: Static route. */
    /* * Option 34: Trailer encapsulation. */
    /* * Option 35: ARP cache timeout. */
    /* * Option 36: Ethernet encapsulation. */
    /* * Option 37: TCP Default TTL. */
    /* * Option 38: TCP Keepalive Interval. */
    /* * Option 39: TCP Keepalive Garbage. */
    /* * Option 40: Network Information Service (NIS) Domain. */
    /* * Option 41: Network Information Servers. */
    /* * Option 42: Network Time Protocol Servers. */
    /* * Option 43: Vendor Specific Information. */
    /* * Option 44: NetBIOS over TCP/IP Name Server (NBNS). */
    /* * Option 45: NetBIOS over TCP/IP Datagram distribution Server (NBDD). */
    /* * Option 46: NetBIOS over TCP/IP Node Type. */
    /* * Option 47: NetBIOS over TCP/IP Scope. */
    /* * Option 48: X Window System Font Server. */
    /* * Option 49: X Window System Display Manager. */

    /** Option 51: IP Address Lease Time. */
    uint32_t                    m_cSecLease;

    /* * Option 64: Network Information Service+ Domain. */
    /* * Option 65: Network Information Service+ Servers. */
    /** Option 66: TFTP server name. */
    std::string                 m_TftpServer;
    /** Address for the bp_siaddr field corresponding to m_TftpServer. */
    RTNETADDRIPV4               m_TftpServerAddr;
    /** Option 67: Bootfile name. */
    std::string                 m_BootfileName;

    /* * Option 68: Mobile IP Home Agent. */
    /* * Option 69: Simple Mail Transport Protocol (SMPT) Server. */
    /* * Option 70: Post Office Protocol (POP3) Server. */
    /* * Option 71: Network News Transport Protocol (NNTP) Server. */
    /* * Option 72: Default World Wide Web (WWW) Server. */
    /* * Option 73: Default Finger Server. */
    /* * Option 74: Default Internet Relay Chat (IRC) Server. */
    /* * Option 75: StreetTalk Server. */

    /* * Option 119: Domain Search. */


    VBoxNetDhcpCfg()
    {
        m_UpperAddr.u = UINT32_MAX;
        m_LowerAddr.u = UINT32_MAX;
        m_SubnetMask.u = UINT32_MAX;
        m_cSecLease = 60*60; /* 1 hour */
    }

    /** Validates the configuration.
     * @returns 0 on success, exit code + error message to stderr on failure. */
    int validate(void)
    {
        if (    m_UpperAddr.u == UINT32_MAX
            ||  m_LowerAddr.u == UINT32_MAX
            ||  m_SubnetMask.u == UINT32_MAX)
        {
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: Config is missing:");
            if (m_UpperAddr.u == UINT32_MAX)
                RTStrmPrintf(g_pStdErr, " --upper-ip");
            if (m_LowerAddr.u == UINT32_MAX)
                RTStrmPrintf(g_pStdErr, " --lower-ip");
            if (m_SubnetMask.u == UINT32_MAX)
                RTStrmPrintf(g_pStdErr, " --netmask");
            return 2;
        }

        if (RT_N2H_U32(m_UpperAddr.u) < RT_N2H_U32(m_LowerAddr.u))
        {
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: The --upper-ip value is lower than the --lower-ip one!\n"
                                    "             %d.%d.%d.%d < %d.%d.%d.%d\n",
                         m_UpperAddr.au8[0], m_UpperAddr.au8[1], m_UpperAddr.au8[2], m_UpperAddr.au8[3],
                         m_LowerAddr.au8[0], m_LowerAddr.au8[1], m_LowerAddr.au8[2], m_LowerAddr.au8[3]);
            return 3;
        }

        /* the code goes insane if we have too many atm. lazy bird */
        uint32_t cIPs = RT_N2H_U32(m_UpperAddr.u) - RT_N2H_U32(m_LowerAddr.u);
        if (cIPs > 1024)
        {
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: Too many IPs between --upper-ip and --lower-ip! %d (max 1024)\n"
                                    "             %d.%d.%d.%d < %d.%d.%d.%d\n",
                         cIPs,
                         m_UpperAddr.au8[0], m_UpperAddr.au8[1], m_UpperAddr.au8[2], m_UpperAddr.au8[3],
                         m_LowerAddr.au8[0], m_LowerAddr.au8[1], m_LowerAddr.au8[2], m_LowerAddr.au8[3]);
            return 3;
        }
        return 0;
    }

    /**
     * Is this config for one specific client?
     *
     * @return  true / false.
     */
    bool            isOneSpecificClient(void) const
    {
        return m_LowerAddr.u == m_UpperAddr.u
            && m_MacAddresses.size() > 0;
    }

    /**
     * Checks if this config matches the specified MAC address.
     *
     * @returns true / false.
     *
     * @param   pMac    The MAC address to match.
     */
    bool            matchesMacAddress(PCRTMAC pMac) const
    {
        size_t i = m_MacAddresses.size();
        if (RT_LIKELY(i < 1))
            return true; /* no entries == ALL wildcard match */

        while (i--)
        {
            PCRTMAC pCur = &m_MacAddresses[i];
            if (    pCur->au16[0] == pMac->au16[0]
                &&  pCur->au16[1] == pMac->au16[1]
                &&  pCur->au16[2] == pMac->au16[2])
                return true;
        }
        return false;
    }

};

/**
 * DHCP lease.
 */
class VBoxNetDhcpLease
{
public:
    typedef enum State
    {
        /** Invalid. */
        kState_Invalid = 0,
        /** The lease is free / released. */
        kState_Free,
        /** An offer has been made.
         * Expire time indicates when the offer expires. */
        kState_Offer,
        /** The lease is active.
         * Expire time indicates when the lease expires. */
        kState_Active
    } State;

    /** The client MAC address. */
    RTMAC           m_MacAddress;
    /** The IPv4 address. */
    RTNETADDRIPV4   m_IPv4Address;

    /** The current lease state. */
    State           m_enmState;
    /** The lease expiration time. */
    RTTIMESPEC      m_ExpireTime;
    /** Transaction ID. */
    uint32_t        m_xid;
    /** The configuration for this lease. */
    VBoxNetDhcpCfg *m_pCfg;

public:
    /** Constructor taking an IPv4 address and a Config. */
    VBoxNetDhcpLease(RTNETADDRIPV4 IPv4Addr, VBoxNetDhcpCfg *pCfg)
    {
        m_pCfg          = pCfg;
        m_IPv4Address   = IPv4Addr;

        m_MacAddress.au16[0] = m_MacAddress.au16[1] =  m_MacAddress.au16[2] = 0xff;
        m_enmState      = kState_Free;
        RTTimeSpecSetSeconds(&m_ExpireTime, 0);
        m_xid           = UINT32_MAX;
    }

    /** Destructor.  */
    ~VBoxNetDhcpLease()
    {
        m_IPv4Address.u = UINT32_MAX;
        m_pCfg          = NULL;
        m_MacAddress.au16[0] = m_MacAddress.au16[1] =  m_MacAddress.au16[2] = 0xff;
        m_enmState      = kState_Free;
        m_xid           = UINT32_MAX;
    }

    void            offer(uint32_t xid);
    void            activate(void);
    void            activate(uint32_t xid);
    void            release(void);
    bool            hasExpired(void) const;

    /**
     * Checks if the lease is in use or not.
     *
     * @returns true if active, false if free or expired.
     *
     * @param   pNow        The current time to use. Optional.
     */
    bool            isInUse(PCRTTIMESPEC pNow = NULL) const
    {
        if  (   m_enmState == kState_Offer
             || m_enmState == kState_Active)
        {
            RTTIMESPEC Now;
            if (!pNow)
                pNow = RTTimeNow(&Now);
            return RTTimeSpecGetSeconds(&m_ExpireTime) > RTTimeSpecGetSeconds(pNow);
        }
        return false;
    }

    /**
     * Is this lease for one specific client?
     *
     * @return  true/false.
     */
    bool            isOneSpecificClient(void) const
    {
        return m_pCfg
            && m_pCfg->isOneSpecificClient();
    }

    /**
     * Is this lease currently being offered to a client.
     *
     * @returns true / false.
     */
    bool            isBeingOffered(void) const
    {
        return m_enmState == kState_Offer
            && isInUse();
    }

    /**
     * Is the lease in the current config or not.
     *
     * When updating the config we might leave active leases behind which aren't
     * included in the new config. These will have m_pCfg set to NULL and should be
     * freed up when they expired.
     *
     * @returns true / false.
     */
    bool            isInCurrentConfig(void) const
    {
        return m_pCfg != NULL;
    }
};

/**
 * DHCP server instance.
 */
class VBoxNetDhcp
{
public:
    VBoxNetDhcp();
    virtual ~VBoxNetDhcp();

    int                 parseArgs(int argc, char **argv);
    int                 tryGoOnline(void);
    int                 run(void);

protected:
    int                 addConfig(VBoxNetDhcpCfg *pCfg);
    void                explodeConfig(void);

    bool                handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb);
    bool                handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb);
    bool                handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb);
    bool                handleDhcpReqDecline(PCRTNETBOOTP pDhcpMsg, size_t cb);
    bool                handleDhcpReqRelease(PCRTNETBOOTP pDhcpMsg, size_t cb);
    void                makeDhcpReply(uint8_t uMsgType, VBoxNetDhcpLease *pLease, PCRTNETBOOTP pDhcpMsg, size_t cb);

    VBoxNetDhcpLease   *findLeaseByMacAddress(PCRTMAC pMacAddress, bool fAnyState);
    VBoxNetDhcpLease   *findLeaseByIpv4AndMacAddresses(RTNETADDRIPV4 IPv4Addr, PCRTMAC pMacAddress, bool fAnyState);
    VBoxNetDhcpLease   *newLease(PCRTNETBOOTP pDhcpMsg, size_t cb);

    static uint8_t const *findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, size_t *pcbMaxOpt);
    static bool         findOptionIPv4Addr(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, PRTNETADDRIPV4 pIPv4Addr);

    inline void         debugPrint( int32_t iMinLevel, bool fMsg,  const char *pszFmt, ...) const;
    void                debugPrintV(int32_t iMinLevel, bool fMsg,  const char *pszFmt, va_list va) const;
    static const char  *debugDhcpName(uint8_t uMsgType);

protected:
    /** @name The server configuration data members.
     * @{ */
    std::string         m_Name;
    std::string         m_Network;
    std::string         m_TrunkName;
    INTNETTRUNKTYPE     m_enmTrunkType;
    RTMAC               m_MacAddress;
    RTNETADDRIPV4       m_Ipv4Address;
    std::string         m_LeaseDBName;
    /** @} */

    /** The current configs. */
    std::vector<VBoxNetDhcpCfg *> m_Cfgs;

    /** The current leases. */
    std::vector<VBoxNetDhcpLease> m_Leases;

    /** @name The network interface
     * @{ */
    PSUPDRVSESSION      m_pSession;
    uint32_t            m_cbSendBuf;
    uint32_t            m_cbRecvBuf;
    INTNETIFHANDLE      m_hIf;          /**< The handle to the network interface. */
    PINTNETBUF          m_pIfBuf;       /**< Interface buffer. */
    /** @} */

    /** @name Debug stuff
     * @{  */
    int32_t             m_cVerbosity;
    uint8_t             m_uCurMsgType;
    size_t              m_cbCurMsg;
    PCRTNETBOOTP        m_pCurMsg;
    VBOXNETUDPHDRS      m_CurHdrs;
    /** @} */
};


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Pointer to the DHCP server. */
static VBoxNetDhcp *g_pDhcp;


/**
 * Offer this lease to a client.
 *
 * @param   xid             The transaction ID.
 */
void VBoxNetDhcpLease::offer(uint32_t xid)
{
    m_enmState = kState_Offer;
    m_xid = xid;
    RTTimeNow(&m_ExpireTime);
    RTTimeSpecAddSeconds(&m_ExpireTime, 60);
}


/**
 * Activate this lease (i.e. a client is now using it).
 */
void VBoxNetDhcpLease::activate(void)
{
    m_enmState = kState_Active;
    RTTimeNow(&m_ExpireTime);
    RTTimeSpecAddSeconds(&m_ExpireTime, m_pCfg ? m_pCfg->m_cSecLease : 60); /* m_pCfg can be NULL right now... */
}


/**
 * Activate this lease with a new transaction ID.
 *
 * @param   xid     The transaction ID.
 * @todo    check if this is really necessary.
 */
void VBoxNetDhcpLease::activate(uint32_t xid)
{
    activate();
    m_xid = xid;
}


/**
 * Release a lease either upon client request or because it didn't quite match a
 * DHCP_REQUEST.
 */
void VBoxNetDhcpLease::release(void)
{
    m_enmState = kState_Free;
    RTTimeNow(&m_ExpireTime);
    RTTimeSpecAddSeconds(&m_ExpireTime, 5);
}


/**
 * Checks if the lease has expired or not.
 *
 * This just checks the expiration time not the state. This is so that this
 * method will work for reusing RELEASEd leases when the client comes back after
 * a reboot or ipconfig /renew. Callers not interested in info on released
 * leases should check the state first.
 *
 * @returns true if expired, false if not.
 */
bool VBoxNetDhcpLease::hasExpired() const
{
    RTTIMESPEC Now;
    return RTTimeSpecGetSeconds(&m_ExpireTime) > RTTimeSpecGetSeconds(RTTimeNow(&Now));
}




/**
 * Construct a DHCP server with a default configuration.
 */
VBoxNetDhcp::VBoxNetDhcp()
{
    m_Name                  = "VBoxNetDhcp";
    m_Network               = "VBoxNetDhcp";
    m_TrunkName             = "";
    m_enmTrunkType          = kIntNetTrunkType_WhateverNone;
    m_MacAddress.au8[0]     = 0x08;
    m_MacAddress.au8[1]     = 0x00;
    m_MacAddress.au8[2]     = 0x27;
    m_MacAddress.au8[3]     = 0x40;
    m_MacAddress.au8[4]     = 0x41;
    m_MacAddress.au8[5]     = 0x42;
    m_Ipv4Address.u         = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10,  0,  2,  5)));

    m_pSession              = NIL_RTR0PTR;
    m_cbSendBuf             =  8192;
    m_cbRecvBuf             = 51200; /** @todo tune to 64 KB with help from SrvIntR0 */
    m_hIf                   = INTNET_HANDLE_INVALID;
    m_pIfBuf                = NULL;

    m_cVerbosity            = 0;
    m_uCurMsgType           = UINT8_MAX;
    m_cbCurMsg              = 0;
    m_pCurMsg               = NULL;
    memset(&m_CurHdrs, '\0', sizeof(m_CurHdrs));

#if 0 /* enable to hack the code without a mile long argument list. */
    VBoxNetDhcpCfg *pDefCfg = new VBoxNetDhcpCfg();
    pDefCfg->m_LowerAddr.u    = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10,  0,  2,100)));
    pDefCfg->m_UpperAddr.u    = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10,  0,  2,250)));
    pDefCfg->m_SubnetMask.u   = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8(255,255,255,  0)));
    RTNETADDRIPV4 Addr;
    Addr.u                    = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10,  0,  2,  1)));
    pDefCfg->m_Routers.push_back(Addr);
    Addr.u                    = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10,  0,  2,  2)));
    pDefCfg->m_DNSes.push_back(Addr);
    pDefCfg->m_DomainName     = "vboxnetdhcp.org";
#if 0
    pDefCfg->m_cSecLease      = 60*60; /* 1 hour */
#else
    pDefCfg->m_cSecLease      = 30; /* sec */
#endif
    pDefCfg->m_TftpServer     = "10.0.2.3"; //??
    this->addConfig(pDefCfg);
#endif
}


/**
 * Destruct a DHCP server.
 */
VBoxNetDhcp::~VBoxNetDhcp()
{
    /*
     * Close the interface connection.
     */
    if (m_hIf != INTNET_HANDLE_INVALID)
    {
        INTNETIFCLOSEREQ CloseReq;
        CloseReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
        CloseReq.Hdr.cbReq = sizeof(CloseReq);
        CloseReq.pSession = m_pSession;
        CloseReq.hIf = m_hIf;
        m_hIf = INTNET_HANDLE_INVALID;
        int rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_CLOSE, 0, &CloseReq.Hdr);
        AssertRC(rc);
    }

    if (m_pSession)
    {
        SUPR3Term(false /*fForced*/);
        m_pSession = NIL_RTR0PTR;
    }
}


/**
 * Adds a config to the tail.
 *
 * @returns See VBoxNetDHCP::validate().
 * @param   pCfg        The config too add.
 *                      This object will be consumed by this call!
 */
int VBoxNetDhcp::addConfig(VBoxNetDhcpCfg *pCfg)
{
    int rc = 0;
    if (pCfg)
    {
        rc = pCfg->validate();
        if (!rc)
            m_Cfgs.push_back(pCfg);
        else
            delete pCfg;
    }
    return rc;
}


/**
 * Explodes the config into leases.
 *
 * @remarks     This code is brute force and not very fast nor memory efficient.
 *              We will have to revisit this later.
 *
 * @remarks     If an IP has been reconfigured for a fixed mac address and it's
 *              already leased to a client, we it won't be available until the
 *              client releases its lease or it expires.
 */
void VBoxNetDhcp::explodeConfig(void)
{
    RTTIMESPEC  Now;
    RTTimeNow(&Now);

    /*
     * Remove all non-active leases from the vector and zapping the
     * config pointers of the once left behind.
     */
    std::vector<VBoxNetDhcpLease>::iterator Itr = m_Leases.begin();
    while (Itr != m_Leases.end())
    {
        if (!Itr->isInUse(&Now))
            Itr = m_Leases.erase(Itr);
        else
        {
            Itr->m_pCfg = NULL;
            Itr++;
        }
    }

    /*
     * Loop thru the configurations in reverse order, giving the last
     * configs priority of the newer ones.
     */
    size_t iCfg = m_Cfgs.size();
    while (iCfg-- > 0)
    {
        VBoxNetDhcpCfg *pCfg = m_Cfgs[iCfg];

        /* Expand the IP lease range. */
        uint32_t const uLast = RT_N2H_U32(pCfg->m_UpperAddr.u);
        for (uint32_t i = RT_N2H_U32(pCfg->m_LowerAddr.u); i <= uLast; i++)
        {
            RTNETADDRIPV4 IPv4Addr;
            IPv4Addr.u = RT_H2N_U32(i);

            /* Check if it exists and is configured. */
            VBoxNetDhcpLease *pLease = NULL;
            for (size_t j = 0; j < m_Leases.size(); j++)
                if (m_Leases[j].m_IPv4Address.u == IPv4Addr.u)
                {
                    pLease = &m_Leases[j];
                    break;
                }
            if (pLease)
            {
                if (!pLease->m_pCfg)
                    pLease->m_pCfg = pCfg;
            }
            else
            {
                /* add it. */
                VBoxNetDhcpLease NewLease(IPv4Addr, pCfg);
                m_Leases.push_back(NewLease);
                debugPrint(10, false, "exploseConfig: new lease %d.%d.%d.%d",
                           IPv4Addr.au8[0], IPv4Addr.au8[1], IPv4Addr.au8[2], IPv4Addr.au8[3]);
            }
        }
    }
}


/**
 * Parse the arguments.
 *
 * @returns 0 on success, fully bitched exit code on failure.
 *
 * @param   argc    Argument count.
 * @param   argv    Argument vector.
 */
int VBoxNetDhcp::parseArgs(int argc, char **argv)
{
    static const RTGETOPTDEF s_aOptionDefs[] =
    {
        { "--name",           'N',   RTGETOPT_REQ_STRING },
        { "--network",        'n',   RTGETOPT_REQ_STRING },
        { "--trunk-name",     't',   RTGETOPT_REQ_STRING },
        { "--trunk-type",     'T',   RTGETOPT_REQ_STRING },
        { "--mac-address",    'a',   RTGETOPT_REQ_MACADDR },
        { "--ip-address",     'i',   RTGETOPT_REQ_IPV4ADDR },
        { "--lease-db",       'D',   RTGETOPT_REQ_STRING },
        { "--verbose",        'v',   RTGETOPT_REQ_NOTHING },

        { "--begin-config",   'b',   RTGETOPT_REQ_NOTHING },
        { "--gateway",        'g',   RTGETOPT_REQ_IPV4ADDR },
        { "--lower-ip",       'l',   RTGETOPT_REQ_IPV4ADDR },
        { "--upper-ip",       'u',   RTGETOPT_REQ_IPV4ADDR },
        { "--netmask",        'm',   RTGETOPT_REQ_IPV4ADDR },
    };

    RTGETOPTSTATE State;
    int rc = RTGetOptInit(&State, argc, argv, &s_aOptionDefs[0], RT_ELEMENTS(s_aOptionDefs), 0, 0 /*fFlags*/);
    AssertRCReturn(rc, 49);

    VBoxNetDhcpCfg *pCurCfg = NULL;
    for (;;)
    {
        RTGETOPTUNION Val;
        rc = RTGetOpt(&State, &Val);
        if (!rc)
            break;
        switch (rc)
        {
            case 'N':
                m_Name = Val.psz;
                break;
            case 'n':
                m_Network = Val.psz;
                break;
            case 't':
                m_TrunkName = Val.psz;
                break;
            case 'T':
                if (!strcmp(Val.psz, "none"))
                    m_enmTrunkType = kIntNetTrunkType_None;
                else if (!strcmp(Val.psz, "whatever"))
                    m_enmTrunkType = kIntNetTrunkType_WhateverNone;
                else if (!strcmp(Val.psz, "netflt"))
                    m_enmTrunkType = kIntNetTrunkType_NetFlt;
                else if (!strcmp(Val.psz, "netadp"))
                    m_enmTrunkType = kIntNetTrunkType_NetAdp;
                else if (!strcmp(Val.psz, "srvnat"))
                    m_enmTrunkType = kIntNetTrunkType_SrvNat;
                else
                {
                    RTStrmPrintf(g_pStdErr, "Invalid trunk type '%s'\n", Val.psz);
                    return 1;
                }
                break;
            case 'a':
                m_MacAddress = Val.MacAddr;
                break;
            case 'i':
                m_Ipv4Address = Val.IPv4Addr;
                break;
            case 'd':
                m_LeaseDBName = Val.psz;
                break;

            case 'v':
                m_cVerbosity++;
                break;

            /* Begin config. */
            case 'b':
                rc = addConfig(pCurCfg);
                if (rc)
                    break;
                pCurCfg = NULL;
                /* fall thru */

            /* config specific ones. */
            case 'g':
            case 'l':
            case 'u':
            case 'm':
                if (!pCurCfg)
                {
                    pCurCfg = new VBoxNetDhcpCfg();
                    if (!pCurCfg)
                    {
                        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: new VBoxDhcpCfg failed\n");
                        return 1;
                    }
                }

                switch (rc)
                {
                    case 'g':
                        pCurCfg->m_Routers.push_back(Val.IPv4Addr);
                        break;

                    case 'l':
                        pCurCfg->m_LowerAddr = Val.IPv4Addr;
                        break;

                    case 'u':
                        pCurCfg->m_UpperAddr = Val.IPv4Addr;
                        break;

                    case 'm':
                        pCurCfg->m_SubnetMask = Val.IPv4Addr;
                        break;

                    case 0: /* ignore */ break;
                    default:
                        AssertMsgFailed(("%d", rc));
                        return 1;
                }
                break;

            case 'V':
                RTPrintf("%sr%u\n", RTBldCfgVersion(), RTBldCfgRevision());
                return 1;

            case 'h':
                RTPrintf("VBoxNetDHCP Version %s\n"
                         "(C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
                         "All rights reserved.\n"
                         "\n"
                         "Usage: VBoxNetDHCP <options>\n"
                         "\n"
                         "Options:\n",
                         RTBldCfgVersion());
                for (size_t i = 0; i < RT_ELEMENTS(s_aOptionDefs); i++)
                    RTPrintf("    -%c, %s\n", s_aOptionDefs[i].iShort, s_aOptionDefs[i].pszLong);
                return 1;

            default:
                rc = RTGetOptPrintError(rc, &Val);
                RTPrintf("Use --help for more information.\n");
                return rc;
        }
    }

    /*
     * Do the reconfig. (move this later)
     */
    if (!rc)
        explodeConfig();

    return rc;
}


/**
 * Tries to connect to the internal network.
 *
 * @returns 0 on success, exit code + error message to stderr on failure.
 */
int VBoxNetDhcp::tryGoOnline(void)
{
    /*
     * Open the session, load ring-0 and issue the request.
     */
    int rc = SUPR3Init(&m_pSession);
    if (RT_FAILURE(rc))
    {
        m_pSession = NIL_RTR0PTR;
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3Init -> %Rrc", rc);
        return 1;
    }

    char szPath[RTPATH_MAX];
    rc = RTPathExecDir(szPath, sizeof(szPath) - sizeof("/VMMR0.r0"));
    if (RT_FAILURE(rc))
    {
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: RTPathProgram -> %Rrc", rc);
        return 1;
    }

    rc = SUPR3LoadVMM(strcat(szPath, "/VMMR0.r0"));
    if (RT_FAILURE(rc))
    {
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3LoadVMM(\"%s\") -> %Rrc", szPath, rc);
        return 1;
    }

    /*
     * Create the open request.
     */
    INTNETOPENREQ OpenReq;
    OpenReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
    OpenReq.Hdr.cbReq = sizeof(OpenReq);
    OpenReq.pSession = m_pSession;
    strncpy(OpenReq.szNetwork, m_Network.c_str(), sizeof(OpenReq.szNetwork));
    OpenReq.szNetwork[sizeof(OpenReq.szNetwork) - 1] = '\0';
    strncpy(OpenReq.szTrunk, m_TrunkName.c_str(), sizeof(OpenReq.szTrunk));
    OpenReq.szTrunk[sizeof(OpenReq.szTrunk) - 1] = '\0';
    OpenReq.enmTrunkType = m_enmTrunkType;
    OpenReq.fFlags = 0; /** @todo check this */
    OpenReq.cbSend = m_cbSendBuf;
    OpenReq.cbRecv = m_cbRecvBuf;
    OpenReq.hIf = INTNET_HANDLE_INVALID;

    /*
     * Issue the request.
     */
    debugPrint(2, false, "attempting to open/create network \"%s\"...", OpenReq.szNetwork);
    rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_OPEN, 0, &OpenReq.Hdr);
    if (RT_SUCCESS(rc))
    {
        m_hIf = OpenReq.hIf;
        debugPrint(1, false, "successfully opened/created \"%s\" - hIf=%#x", OpenReq.szNetwork, m_hIf);

        /*
         * Get the ring-3 address of the shared interface buffer.
         */
        INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq;
        GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
        GetBufferPtrsReq.Hdr.cbReq = sizeof(GetBufferPtrsReq);
        GetBufferPtrsReq.pSession = m_pSession;
        GetBufferPtrsReq.hIf = m_hIf;
        GetBufferPtrsReq.pRing3Buf = NULL;
        GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR;
        rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, 0, &GetBufferPtrsReq.Hdr);
        if (RT_SUCCESS(rc))
        {
            PINTNETBUF pBuf = GetBufferPtrsReq.pRing3Buf;
            debugPrint(1, false, "pBuf=%p cbBuf=%d cbSend=%d cbRecv=%d",
                       pBuf, pBuf->cbBuf, pBuf->cbSend, pBuf->cbRecv);
            m_pIfBuf = pBuf;

            /*
             * Activate the interface.
             */
            INTNETIFSETACTIVEREQ ActiveReq;
            ActiveReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
            ActiveReq.Hdr.cbReq = sizeof(ActiveReq);
            ActiveReq.pSession = m_pSession;
            ActiveReq.hIf = m_hIf;
            ActiveReq.fActive = true;
            rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SET_ACTIVE, 0, &ActiveReq.Hdr);
            if (RT_SUCCESS(rc))
                return 0;

            /* bail out */
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE,) failed, rc=%Rrc\n", rc);
        }
        else
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS,) failed, rc=%Rrc\n", rc);
    }
    else
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_OPEN,) failed, rc=%Rrc\n", rc);

    return RT_SUCCESS(rc) ? 0 : 1;
}


/**
 * Runs the DHCP server.
 *
 * @returns exit code + error message to stderr on failure, won't return on
 *          success (you must kill this process).
 */
int VBoxNetDhcp::run(void)
{
    /*
     * The loop.
     */
    PINTNETRINGBUF  pRingBuf = &m_pIfBuf->Recv;
    for (;;)
    {
        /*
         * Wait for a packet to become available.
         */
        INTNETIFWAITREQ WaitReq;
        WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC;
        WaitReq.Hdr.cbReq = sizeof(WaitReq);
        WaitReq.pSession = m_pSession;
        WaitReq.hIf = m_hIf;
        WaitReq.cMillies = 2000; /* 2 secs - the sleep is for some reason uninterruptible... */  /** @todo fix interruptability in SrvIntNet! */
        int rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_WAIT, 0, &WaitReq.Hdr);
        if (RT_FAILURE(rc))
        {
            if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED)
                continue;
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: VMMR0_DO_INTNET_IF_WAIT returned %Rrc\n", rc);
            return 1;
        }

        /*
         * Process the receive buffer.
         */
        while (IntNetRingHasMoreToRead(pRingBuf))
        {
            size_t  cb;
            void   *pv = VBoxNetUDPMatch(m_pIfBuf, RTNETIPV4_PORT_BOOTPS, &m_MacAddress,
                                         VBOXNETUDP_MATCH_UNICAST | VBOXNETUDP_MATCH_BROADCAST | VBOXNETUDP_MATCH_CHECKSUM
                                         | (m_cVerbosity > 2 ? VBOXNETUDP_MATCH_PRINT_STDERR : 0),
                                         &m_CurHdrs, &cb);
            if (pv && cb)
            {
                PCRTNETBOOTP pDhcpMsg = (PCRTNETBOOTP)pv;
                m_pCurMsg  = pDhcpMsg;
                m_cbCurMsg = cb;

                uint8_t uMsgType;
                if (RTNetIPv4IsDHCPValid(NULL /* why is this here? */, pDhcpMsg, cb, &uMsgType))
                {
                    m_uCurMsgType = uMsgType;
                    handleDhcpMsg(uMsgType, pDhcpMsg, cb);
                    m_uCurMsgType = UINT8_MAX;
                }
                else
                    debugPrint(1, true, "VBoxNetDHCP: Skipping invalid DHCP packet.\n"); /** @todo handle pure bootp clients too? */

                m_pCurMsg = NULL;
                m_cbCurMsg = 0;
            }
            else if (VBoxNetArpHandleIt(m_pSession, m_hIf, m_pIfBuf, &m_MacAddress, m_Ipv4Address))
            {
                /* nothing */
            }

            /* Advance to the next frame. */
            IntNetRingSkipFrame(pRingBuf);
        }
    }

    return 0;
}


/**
 * Handles a DHCP message.
 *
 * @returns true if handled, false if not.
 * @param   uMsgType        The message type.
 * @param   pDhcpMsg        The DHCP message.
 * @param   cb              The size of the DHCP message.
 */
bool VBoxNetDhcp::handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    if (pDhcpMsg->bp_op == RTNETBOOTP_OP_REQUEST)
    {
        switch (uMsgType)
        {
            case RTNET_DHCP_MT_DISCOVER:
                return handleDhcpReqDiscover(pDhcpMsg, cb);

            case RTNET_DHCP_MT_REQUEST:
                return handleDhcpReqRequest(pDhcpMsg, cb);

            case RTNET_DHCP_MT_DECLINE:
                return handleDhcpReqDecline(pDhcpMsg, cb);

            case RTNET_DHCP_MT_RELEASE:
                return handleDhcpReqRelease(pDhcpMsg, cb);

            case RTNET_DHCP_MT_INFORM:
                debugPrint(0, true, "Should we handle this?");
                break;

            default:
                debugPrint(0, true, "Unexpected.");
                break;
        }
    }
    return false;
}


/**
 * The client is requesting an offer.
 *
 * @returns true.
 *
 * @param   pDhcpMsg    The message.
 * @param   cb          The message size.
 */
bool VBoxNetDhcp::handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    /*
     * The newLease() method contains logic for finding current leases
     * and reusing them in case the client is forgetful.
     */
    VBoxNetDhcpLease *pLease = newLease(pDhcpMsg, cb);
    if (!pLease)
        return false;
    debugPrint(1, true, "Offering %d.%d.%d.%d to %.6Rhxs xid=%#x",
               pLease->m_IPv4Address.au8[0],
               pLease->m_IPv4Address.au8[1],
               pLease->m_IPv4Address.au8[2],
               pLease->m_IPv4Address.au8[3],
               &pDhcpMsg->bp_chaddr.Mac,
               pDhcpMsg->bp_xid);
    pLease->offer(pDhcpMsg->bp_xid);

    makeDhcpReply(RTNET_DHCP_MT_OFFER, pLease, pDhcpMsg, cb);
    return true;
}


/**
 * The client is requesting an offer.
 *
 * @returns true.
 *
 * @param   pDhcpMsg    The message.
 * @param   cb          The message size.
 */
bool VBoxNetDhcp::handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    /** @todo Probably need to match the server IP here to work correctly with
     *        other servers. */
    /** @todo This code isn't entirely correct and quite a bit of a hack, but it
     *        will have to do for now as the right thing (tm) is very complex.
     *        Part of the fun is verifying that the request is something we can
     *        and should handle. */

    /*
     * Try find the lease by the requested address + client MAC address.
     */
    VBoxNetDhcpLease   *pLease = NULL;
    RTNETADDRIPV4       IPv4Addr;
    bool                fReqAddr = findOptionIPv4Addr(RTNET_DHCP_OPT_REQ_ADDR, pDhcpMsg, cb, &IPv4Addr);
    if (fReqAddr)
    {
        fReqAddr = true;
        pLease = findLeaseByIpv4AndMacAddresses(IPv4Addr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */);
    }

    /*
     * Try find the lease by the client IP address + client MAC address.
     */
    if (    !pLease
        &&  pDhcpMsg->bp_ciaddr.u)
        pLease = findLeaseByIpv4AndMacAddresses(pDhcpMsg->bp_ciaddr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */);

#if 0 /** @todo client id stuff - it doesn't make sense here imho, we need IP + MAC. What would make sense
                though is to compare the client id with what we've got in the lease and use it to root out
                bad requests. */
    /*
     * Try find the lease by using the client id.
     */
    if (!pLease)
    {
        size_t          cbClientID = 0;
        uint8_t const  *pbClientID  = findOption(RTNET_DHCP_OPT_CLIENT_ID, pDhcpMsg, cb, &cbClientID);
        if (    pbClientID
            &&  cbClientID == sizeof(RTMAC) + 1
            &&  pbClientID[0] == RTNET_ARP_ETHER
            &&
                )
        {
            pLease = findLeaseByIpv4AndMacAddresses(pDhcpMsg->bp_ciaddr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */);
        }
    }
#endif

    /*
     * Validate the lease that's requested.
     * We've already check the MAC and IP addresses.
     */
    bool fAckIt = false;
    if (pLease)
    {
        if (pLease->isBeingOffered())
        {
            if (pLease->m_xid == pDhcpMsg->bp_xid)
                debugPrint(2, true, "REQUEST for offered lease.");
            else
                debugPrint(2, true, "REQUEST for offered lease, xid mismatch. Expected %#x, got %#x.",
                           pLease->m_xid, pDhcpMsg->bp_xid);
            pLease->activate(pDhcpMsg->bp_xid);
            fAckIt = true;
        }
        else if (!pLease->isInCurrentConfig())
            debugPrint(1, true, "REQUEST for obsolete lease -> NAK");
        else if (fReqAddr != (pDhcpMsg->bp_ciaddr.u != 0)) // ???
        {
            /** @todo this ain't safe. */
            debugPrint(1, true, "REQUEST for lease not on offer, assuming renewal. lease_xid=%#x bp_xid=%#x",
                       pLease->m_xid, pDhcpMsg->bp_xid);
            fAckIt = true;
            pLease->activate(pDhcpMsg->bp_xid);
        }
        else
            debugPrint(1, true, "REQUEST for lease not on offer, NAK it.");
    }

    /*
     * NAK if if no lease was found.
     */
    if (fAckIt)
    {
        debugPrint(1, false, "ACK'ing DHCP_REQUEST");
        makeDhcpReply(RTNET_DHCP_MT_ACK, pLease, pDhcpMsg, cb);
    }
    else
    {
        debugPrint(1, false, "NAK'ing DHCP_REQUEST");
        makeDhcpReply(RTNET_DHCP_MT_NAC, NULL, pDhcpMsg, cb);
    }

    return true;
}


/**
 * The client is declining an offer we've made.
 *
 * @returns true.
 *
 * @param   pDhcpMsg    The message.
 * @param   cb          The message size.
 */
bool VBoxNetDhcp::handleDhcpReqDecline(PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    /** @todo Probably need to match the server IP here to work correctly with
     *        other servers. */

    /*
     * The client is supposed to pass us option 50, requested address,
     * from the offer. We also match the lease state. Apparently the
     * MAC address is not supposed to be checked here.
     */

    /** @todo this is not required in the initial implementation, do it later. */
    debugPrint(1, true, "DECLINE is not implemented");
    return true;
}


/**
 * The client is releasing its lease - good boy.
 *
 * @returns true.
 *
 * @param   pDhcpMsg    The message.
 * @param   cb          The message size.
 */
bool VBoxNetDhcp::handleDhcpReqRelease(PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    /** @todo Probably need to match the server IP here to work correctly with
     *        other servers. */

    /*
     * The client may pass us option 61, client identifier, which we should
     * use to find the lease by.
     *
     * We're matching MAC address and lease state as well.
     */

    /*
     * If no client identifier or if we couldn't find a lease by using it,
     * we will try look it up by the client IP address.
     */


    /*
     * If found, release it.
     */


    /** @todo this is not required in the initial implementation, do it later. */
    debugPrint(1, true, "RELEASE is not implemented");
    return true;
}


/**
 * Helper class for stuffing DHCP options into a reply packet.
 */
class VBoxNetDhcpWriteCursor
{
private:
    uint8_t        *m_pbCur;       /**< The current cursor position. */
    uint8_t        *m_pbEnd;       /**< The end the current option space. */
    uint8_t        *m_pfOverload;  /**< Pointer to the flags of the overload option. */
    uint8_t         m_fUsed;       /**< Overload fields that have been used. */
    PRTNETDHCPOPT   m_pOpt;        /**< The current option. */
    PRTNETBOOTP     m_pDhcp;       /**< The DHCP packet. */
    bool            m_fOverflowed; /**< Set if we've overflowed, otherwise false. */

public:
    /** Instantiate an option cursor for the specified DHCP message. */
    VBoxNetDhcpWriteCursor(PRTNETBOOTP pDhcp, size_t cbDhcp) :
        m_pbCur(&pDhcp->bp_vend.Dhcp.dhcp_opts[0]),
        m_pbEnd((uint8_t *)pDhcp + cbDhcp),
        m_pfOverload(NULL),
        m_fUsed(0),
        m_pOpt(NULL),
        m_pDhcp(pDhcp),
        m_fOverflowed(false)
    {
        AssertPtr(pDhcp);
        Assert(cbDhcp > RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts[10]));
    }

    /** Destructor.  */
    ~VBoxNetDhcpWriteCursor()
    {
        m_pbCur = m_pbEnd = m_pfOverload = NULL;
        m_pOpt = NULL;
        m_pDhcp = NULL;
    }

    /**
     * Try use the bp_file field.
     * @returns true if not overloaded, false otherwise.
     */
    bool useBpFile(void)
    {
        if (    m_pfOverload
            &&  (*m_pfOverload & 1))
            return false;
        m_fUsed |= 1 /* bp_file flag*/;
        return true;
    }


    /**
     * Try overload more BOOTP fields
     */
    bool overloadMore(void)
    {
        /* switch option area. */
        uint8_t    *pbNew;
        uint8_t    *pbNewEnd;
        uint8_t     fField;
        if (!(m_fUsed & 1))
        {
            fField     = 1;
            pbNew      = &m_pDhcp->bp_file[0];
            pbNewEnd   = &m_pDhcp->bp_file[sizeof(m_pDhcp->bp_file)];
        }
        else if (!(m_fUsed & 2))
        {
            fField     = 2;
            pbNew      = &m_pDhcp->bp_sname[0];
            pbNewEnd   = &m_pDhcp->bp_sname[sizeof(m_pDhcp->bp_sname)];
        }
        else
            return false;

        if (!m_pfOverload)
        {
            /* Add an overload option. */
            *m_pbCur++ = RTNET_DHCP_OPT_OPTION_OVERLOAD;
            *m_pbCur++ = fField;
            m_pfOverload = m_pbCur;
            *m_pbCur++ = 1;     /* bp_file flag */
        }
        else
            *m_pfOverload |= fField;

        /* pad current option field */
        while (m_pbCur != m_pbEnd)
            *m_pbCur++ = RTNET_DHCP_OPT_PAD; /** @todo not sure if this stuff is at all correct... */

        /* switch */
        m_pbCur = pbNew;
        m_pbEnd = pbNewEnd;
        return true;
    }

    /**
     * Begin an option.
     *
     * @returns true on success, false if we're out of space.
     *
     * @param   uOption     The option number.
     * @param   cb          The amount of data.
     */
    bool begin(uint8_t uOption, size_t cb)
    {
        /* Check that the data of the previous option has all been written. */
        Assert(   !m_pOpt
               || (m_pbCur - m_pOpt->dhcp_len == (uint8_t *)(m_pOpt + 1)));
        AssertMsg(cb <= 255, ("%#x\n", cb));

        /* Check if we need to overload more stuff. */
        if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + (m_pfOverload ? 1 : 3))
        {
            m_pOpt = NULL;
            if (!overloadMore())
            {
                m_fOverflowed = true;
                AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false);
            }
            if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + 1)
            {
                m_fOverflowed = true;
                AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false);
            }
        }

        /* Emit the option header. */
        m_pOpt = (PRTNETDHCPOPT)m_pbCur;
        m_pOpt->dhcp_opt = uOption;
        m_pOpt->dhcp_len = (uint8_t)cb;
        m_pbCur += 2;
        return true;
    }

    /**
     * Puts option data.
     *
     * @param   pvData      The data.
     * @param   cb          The amount to put.
     */
    void put(void const *pvData, size_t cb)
    {
        Assert(m_pOpt || m_fOverflowed);
        if (RT_LIKELY(m_pOpt))
        {
            Assert((uintptr_t)m_pbCur - (uintptr_t)(m_pOpt + 1) + cb  <= (size_t)m_pOpt->dhcp_len);
            memcpy(m_pbCur, pvData, cb);
            m_pbCur += cb;
        }
    }

    /**
     * Puts an IPv4 Address.
     *
     * @param   IPv4Addr    The address.
     */
    void putIPv4Addr(RTNETADDRIPV4 IPv4Addr)
    {
        put(&IPv4Addr, 4);
    }

    /**
     * Adds an IPv4 address option.
     *
     * @returns true/false just like begin().
     *
     * @param   uOption     The option number.
     * @param   IPv4Addr    The address.
     */
    bool optIPv4Addr(uint8_t uOption, RTNETADDRIPV4 IPv4Addr)
    {
        if (!begin(uOption, 4))
            return false;
        putIPv4Addr(IPv4Addr);
        return true;
    }

    /**
     * Adds an option taking 1 or more IPv4 address.
     *
     * If the vector contains no addresses, the option will not be added.
     *
     * @returns true/false just like begin().
     *
     * @param   uOption     The option number.
     * @param   rIPv4Addrs  Reference to the address vector.
     */
    bool optIPv4Addrs(uint8_t uOption, std::vector<RTNETADDRIPV4> const &rIPv4Addrs)
    {
        size_t const c = rIPv4Addrs.size();
        if (!c)
            return true;

        if (!begin(uOption, 4*c))
            return false;
        for (size_t i = 0; i < c; i++)
            putIPv4Addr(rIPv4Addrs[i]);
        return true;
    }

    /**
     * Puts an 8-bit integer.
     *
     * @param   u8          The integer.
     */
    void putU8(uint8_t u8)
    {
        put(&u8, 1);
    }

    /**
     * Adds an 8-bit integer option.
     *
     * @returns true/false just like begin().
     *
     * @param   uOption     The option number.
     * @param   u8          The integer
     */
    bool optU8(uint8_t uOption, uint8_t u8)
    {
        if (!begin(uOption, 1))
            return false;
        putU8(u8);
        return true;
    }

    /**
     * Puts an 32-bit integer (network endian).
     *
     * @param   u32Network  The integer.
     */
    void putU32(uint32_t u32)
    {
        put(&u32, 4);
    }

    /**
     * Adds an 32-bit integer (network endian) option.
     *
     * @returns true/false just like begin().
     *
     * @param   uOption     The option number.
     * @param   u32Network  The integer.
     */
    bool optU32(uint8_t uOption, uint32_t u32)
    {
        if (!begin(uOption, 4))
            return false;
        putU32(u32);
        return true;
    }

    /**
     * Puts a std::string.
     *
     * @param   rStr        Reference to the string.
     */
    void putStr(std::string const &rStr)
    {
        put(rStr.c_str(), rStr.size());
    }

    /**
     * Adds an std::string option if the string isn't empty.
     *
     * @returns true/false just like begin().
     *
     * @param   uOption     The option number.
     * @param   rStr        Reference to the string.
     */
    bool optStr(uint8_t uOption, std::string const &rStr)
    {
        const size_t cch = rStr.size();
        if (!cch)
            return true;

        if (!begin(uOption, cch))
            return false;
        put(rStr.c_str(), cch);
        return true;
    }

    /**
     * Whether we've overflowed.
     *
     * @returns true on overflow, false otherwise.
     */
    bool hasOverflowed(void) const
    {
        return m_fOverflowed;
    }

    /**
     * Adds the terminating END option.
     *
     * The END will always be added as we're reserving room for it, however, we
     * might have dropped previous options due to overflows and that is what the
     * return status indicates.
     *
     * @returns true on success, false on a (previous) overflow.
     */
    bool optEnd(void)
    {
        Assert((uintptr_t)(m_pbEnd - m_pbCur) < 4096);
        *m_pbCur++ = RTNET_DHCP_OPT_END;
        return !hasOverflowed();
    }
};


/**
 * Constructs and sends a reply to a client.
 *
 * @returns
 * @param   uMsgType        The DHCP message type.
 * @param   pLease          The lease. This can be NULL for some replies.
 * @param   pDhcpMsg        The client message. We will dig out the MAC address,
 *                          transaction ID, and requested options from this.
 * @param   cb              The size of the client message.
 */
void VBoxNetDhcp::makeDhcpReply(uint8_t uMsgType, VBoxNetDhcpLease *pLease, PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    size_t      cbReply = RTNET_DHCP_NORMAL_SIZE; /** @todo respect the RTNET_DHCP_OPT_MAX_DHCP_MSG_SIZE option */
    PRTNETBOOTP pReply = (PRTNETBOOTP)alloca(cbReply);

    /*
     * The fixed bits stuff.
     */
    pReply->bp_op     = RTNETBOOTP_OP_REPLY;
    pReply->bp_htype  = RTNET_ARP_ETHER;
    pReply->bp_hlen   = sizeof(RTMAC);
    pReply->bp_hops   = 0;
    pReply->bp_xid    = pDhcpMsg->bp_xid;
    pReply->bp_secs   = 0;
    pReply->bp_flags  = 0; // (pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST); ??
    pReply->bp_ciaddr.u = 0;
    pReply->bp_yiaddr.u = pLease ? pLease->m_IPv4Address.u : 0xffffffff;
    pReply->bp_siaddr.u = pLease && pLease->m_pCfg ? pLease->m_pCfg->m_TftpServerAddr.u : 0; /* (next server == TFTP)*/
    pReply->bp_giaddr.u = 0;
    memset(&pReply->bp_chaddr, '\0', sizeof(pReply->bp_chaddr));
    pReply->bp_chaddr.Mac = pDhcpMsg->bp_chaddr.Mac;
    memset(&pReply->bp_sname[0], '\0', sizeof(pReply->bp_sname));
    memset(&pReply->bp_file[0],  '\0', sizeof(pReply->bp_file));
    pReply->bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE);
    memset(&pReply->bp_vend.Dhcp.dhcp_opts[0], '\0', RTNET_DHCP_OPT_SIZE);

    /*
     * The options - use a cursor class for dealing with the ugly stuff.
     */
    VBoxNetDhcpWriteCursor Cursor(pReply, cbReply);

    /* The basics */
    Cursor.optU8(RTNET_DHCP_OPT_MSG_TYPE, uMsgType);
    Cursor.optIPv4Addr(RTNET_DHCP_OPT_SERVER_ID, m_Ipv4Address);

    if (uMsgType != RTNET_DHCP_MT_NAC)
    {
        AssertReturnVoid(pLease && pLease->m_pCfg);
        const VBoxNetDhcpCfg *pCfg = pLease->m_pCfg; /* no need to retain it. */

        /* The IP config. */
        Cursor.optU32(RTNET_DHCP_OPT_LEASE_TIME, RT_H2N_U32(pCfg->m_cSecLease));
        Cursor.optIPv4Addr(RTNET_DHCP_OPT_SUBNET_MASK, pCfg->m_SubnetMask);
        Cursor.optIPv4Addrs(RTNET_DHCP_OPT_ROUTERS, pCfg->m_Routers);
        Cursor.optIPv4Addrs(RTNET_DHCP_OPT_ROUTERS, pCfg->m_DNSes);
        Cursor.optStr(RTNET_DHCP_OPT_HOST_NAME, pCfg->m_HostName);
        Cursor.optStr(RTNET_DHCP_OPT_DOMAIN_NAME, pCfg->m_DomainName);

        /* The PXE config. */
        if (pCfg->m_BootfileName.size())
        {
            if (Cursor.useBpFile())
                RTStrPrintf((char *)&pReply->bp_file[0], sizeof(pReply->bp_file), "%s", pCfg->m_BootfileName.c_str());
            else
                Cursor.optStr(RTNET_DHCP_OPT_BOOTFILE_NAME, pCfg->m_BootfileName);
        }
    }

    /* Terminate the options. */
    if (!Cursor.optEnd())
        debugPrint(0, true, "option overflow\n");

    /*
     * Send it.
     */
    int rc;
#if 0
    if (!(pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST)) /** @todo need to see someone set this flag to check that it's correct. */
    {
        RTNETADDRIPV4 IPv4AddrBrdCast;
        IPv4AddrBrdCast.u = UINT32_C(0xffffffff); /* broadcast IP */
        rc = VBoxNetUDPUnicast(m_pSession, m_hIf, m_pIfBuf,
                               m_Ipv4Address, &m_MacAddress, RTNETIPV4_PORT_BOOTPS,                 /* sender */
                               IPv4AddrBrdCast, &pDhcpMsg->bp_chaddr.Mac, RTNETIPV4_PORT_BOOTPC,    /* receiver */
                               pReply, cbReply);
    }
    else
#endif
        rc = VBoxNetUDPBroadcast(m_pSession, m_hIf, m_pIfBuf,
                                 m_Ipv4Address, &m_MacAddress, RTNETIPV4_PORT_BOOTPS,               /* sender */
                                 RTNETIPV4_PORT_BOOTPC,                                             /* receiver port */
                                 pReply, cbReply);
    if (RT_FAILURE(rc))
        debugPrint(0, true, "error %Rrc when sending the reply", rc);
}


/**
 * Look up a lease by MAC address.
 *
 * @returns Pointer to the lease if found, NULL if not found.
 * @param   pMacAddress             The mac address.
 * @param   fAnyState       Any state.
 */
VBoxNetDhcpLease *VBoxNetDhcp::findLeaseByMacAddress(PCRTMAC pMacAddress, bool fAnyState)
{
    size_t iLease = m_Leases.size();
    while (iLease-- > 0)
    {
        VBoxNetDhcpLease *pLease = &m_Leases[iLease];
        if (    pLease
            &&  pLease->m_MacAddress.au16[0] == pMacAddress->au16[0]
            &&  pLease->m_MacAddress.au16[1] == pMacAddress->au16[1]
            &&  pLease->m_MacAddress.au16[2] == pMacAddress->au16[2]
            &&  (   fAnyState
                 || (pLease->m_enmState != VBoxNetDhcpLease::kState_Free)) )
            return pLease;
    }

    return NULL;
}


/**
 * Look up a lease by IPv4 and MAC addresses.
 *
 * @returns Pointer to the lease if found, NULL if not found.
 * @param   IPv4Addr        The IPv4 address.
 * @param   pMacAddress     The mac address.
 * @param   fAnyState       Any state.
 */
VBoxNetDhcpLease *VBoxNetDhcp::findLeaseByIpv4AndMacAddresses(RTNETADDRIPV4 IPv4Addr, PCRTMAC pMacAddress, bool fAnyState)
{
    size_t iLease = m_Leases.size();
    while (iLease-- > 0)
    {
        VBoxNetDhcpLease *pLease = &m_Leases[iLease];
        if (    pLease
            &&  pLease->m_IPv4Address.u      == IPv4Addr.u
            &&  pLease->m_MacAddress.au16[0] == pMacAddress->au16[0]
            &&  pLease->m_MacAddress.au16[1] == pMacAddress->au16[1]
            &&  pLease->m_MacAddress.au16[2] == pMacAddress->au16[2]
            &&  (   fAnyState
                 || (pLease->m_enmState != VBoxNetDhcpLease::kState_Free)) )
            return pLease;
    }

    return NULL;
}


/**
 * Creates a new lease for the client specified in the DHCP message.
 *
 * The caller has already made sure it doesn't already have a lease.
 *
 * @returns Pointer to the lease if found, NULL+log if not found.
 * @param   IPv4Addr        The IPv4 address.
 * @param   pMacAddress     The MAC address.
 */
VBoxNetDhcpLease *VBoxNetDhcp::newLease(PCRTNETBOOTP pDhcpMsg, size_t cb)
{
    RTMAC const MacAddr = pDhcpMsg->bp_chaddr.Mac;
    RTTIMESPEC  Now;
    RTTimeNow(&Now);

    /*
     * Search the possible leases.
     *
     * We'll try do all the searches in one pass, that is to say, perfect
     * match, old lease, and next free/expired lease.
     */
    VBoxNetDhcpLease *pBest = NULL;
    VBoxNetDhcpLease *pOld  = NULL;
    VBoxNetDhcpLease *pFree = NULL;

    size_t cLeases = m_Leases.size();
    for (size_t i = 0; i < cLeases; i++)
    {
        VBoxNetDhcpLease *pCur = &m_Leases[i];

        /* Skip it if no configuration, that means its not in the current config. */
        if (!pCur->m_pCfg)
            continue;

        /* best */
        if (    pCur->isOneSpecificClient()
            &&  pCur->m_pCfg->matchesMacAddress(&MacAddr))
        {
            if (    !pBest
                ||  pBest->m_pCfg->m_MacAddresses.size() < pCur->m_pCfg->m_MacAddresses.size())
                pBest = pCur;
        }

        /* old lease */
        if (    pCur->m_MacAddress.au16[0] == MacAddr.au16[0]
            &&  pCur->m_MacAddress.au16[1] == MacAddr.au16[1]
            &&  pCur->m_MacAddress.au16[2] == MacAddr.au16[2])
        {
            if (    !pOld
                ||  RTTimeSpecGetSeconds(&pCur->m_ExpireTime) > RTTimeSpecGetSeconds(&pFree->m_ExpireTime))
                pOld = pCur;
        }

        /* expired lease */
        if (!pCur->isInUse(&Now))
        {
            if (    !pFree
                ||  RTTimeSpecGetSeconds(&pCur->m_ExpireTime) < RTTimeSpecGetSeconds(&pFree->m_ExpireTime))
                pFree = pCur;
        }
    }

    VBoxNetDhcpLease *pNew = pBest;
    if (!pNew)
        pNew = pOld;
    if (!pNew)
        pNew = pFree;
    if (!pNew)
    {
        debugPrint(0, true, "No more leases.");
        return NULL;
    }

    /*
     * Init the lease.
     */
    pNew->m_MacAddress = MacAddr;
    pNew->m_xid        = pDhcpMsg->bp_xid;
    /** @todo extract the client id. */

    return pNew;
}


/**
 * Finds an option.
 *
 * @returns On success, a pointer to the first byte in the option data (no none
 *          then it'll be the byte following the 0 size field) and *pcbOpt set
 *          to the option length.
 *          On failure, NULL is returned and *pcbOpt unchanged.
 *
 * @param   uOption         The option to search for.
 * @param   pDhcpMsg        The DHCP message.
 * @param   cb              The size of the message.
 * @param   pcbOpt          Where to store the option size size. Optional. Note
 *                          that this is adjusted if the option length is larger
 *                          than the message buffer.
 */
/* static */ const uint8_t *
VBoxNetDhcp::findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, size_t *pcbOpt)
{
    Assert(uOption != RTNET_DHCP_OPT_PAD);

    /*
     * Validate the DHCP bits and figure the max size of the options in the vendor field.
     */
    if (cb <= RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts))
        return NULL;
    if (pDhcpMsg->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE))
        return NULL;
    size_t cbLeft = cb - RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts);
    if (cbLeft > RTNET_DHCP_OPT_SIZE)
        cbLeft = RTNET_DHCP_OPT_SIZE;

    /*
     * Search the vendor field.
     */
    bool            fExtended = false;
    uint8_t const  *pb = &pDhcpMsg->bp_vend.Dhcp.dhcp_opts[0];
    while (pb && cbLeft > 0)
    {
        uint8_t uCur  = *pb;
        if (uCur == RTNET_DHCP_OPT_PAD)
        {
            cbLeft--;
            pb++;
        }
        else if (cbLeft <= 1)
            break;
        else
        {
            size_t  cbCur = pb[1];
            if (cbCur > cbLeft - 2)
                cbCur = cbLeft - 2;
            if (uCur == uOption)
            {
                if (pcbOpt)
                    *pcbOpt = cbCur;
                return pb+2;
            }
            pb     += cbCur + 2;
            cbLeft -= cbCur - 2;
        }
    }

    /** @todo search extended dhcp option field(s) when present */

    return NULL;
}


/**
 * Locates an option with an IPv4 address in the DHCP message.
 *
 * @returns true and *pIpv4Addr if found, false if not.
 *
 * @param   uOption         The option to find.
 * @param   pDhcpMsg        The DHCP message.
 * @param   cb              The size of the message.
 * @param   pIPv4Addr       Where to put the address.
 */
/* static */ bool
VBoxNetDhcp::findOptionIPv4Addr(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, PRTNETADDRIPV4 pIPv4Addr)
{
    size_t          cbOpt;
    uint8_t const  *pbOpt = findOption(uOption, pDhcpMsg, cb, &cbOpt);
    if (pbOpt)
    {
        if (cbOpt >= sizeof(RTNETADDRIPV4))
        {
            *pIPv4Addr = *(PCRTNETADDRIPV4)pbOpt;
            return true;
        }
    }
    return false;
}


/**
 * Print debug message depending on the m_cVerbosity level.
 *
 * @param   iMinLevel       The minimum m_cVerbosity level for this message.
 * @param   fMsg            Whether to dump parts for the current DHCP message.
 * @param   pszFmt          The message format string.
 * @param   ...             Optional arguments.
 */
inline void VBoxNetDhcp::debugPrint(int32_t iMinLevel, bool fMsg, const char *pszFmt, ...) const
{
    if (iMinLevel <= m_cVerbosity)
    {
        va_list va;
        va_start(va, pszFmt);
        debugPrintV(iMinLevel, fMsg, pszFmt, va);
        va_end(va);
    }
}


/**
 * Print debug message depending on the m_cVerbosity level.
 *
 * @param   iMinLevel       The minimum m_cVerbosity level for this message.
 * @param   fMsg            Whether to dump parts for the current DHCP message.
 * @param   pszFmt          The message format string.
 * @param   va              Optional arguments.
 */
void VBoxNetDhcp::debugPrintV(int iMinLevel, bool fMsg, const char *pszFmt, va_list va) const
{
    if (iMinLevel <= m_cVerbosity)
    {
        va_list vaCopy;                 /* This dude is *very* special, thus the copy. */
        va_copy(vaCopy, va);
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: %s: %N\n", iMinLevel >= 2 ? "debug" : "info", pszFmt, &vaCopy);
        va_end(vaCopy);

        if (    fMsg
            &&  m_cVerbosity >= 2
            &&  m_pCurMsg)
        {
            const char *pszMsg = m_uCurMsgType != UINT8_MAX ? debugDhcpName(m_uCurMsgType) : "";
            RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: debug: %8s chaddr=%.6Rhxs ciaddr=%d.%d.%d.%d yiaddr=%d.%d.%d.%d siaddr=%d.%d.%d.%d xid=%#x\n",
                         pszMsg,
                         &m_pCurMsg->bp_chaddr,
                         m_pCurMsg->bp_ciaddr.au8[0], m_pCurMsg->bp_ciaddr.au8[1], m_pCurMsg->bp_ciaddr.au8[2], m_pCurMsg->bp_ciaddr.au8[3],
                         m_pCurMsg->bp_yiaddr.au8[0], m_pCurMsg->bp_yiaddr.au8[1], m_pCurMsg->bp_yiaddr.au8[2], m_pCurMsg->bp_yiaddr.au8[3],
                         m_pCurMsg->bp_siaddr.au8[0], m_pCurMsg->bp_siaddr.au8[1], m_pCurMsg->bp_siaddr.au8[2], m_pCurMsg->bp_siaddr.au8[3],
                         m_pCurMsg->bp_xid);
        }
    }
}


/**
 * Gets the name of given DHCP message type.
 *
 * @returns Readonly name.
 * @param   uMsgType        The message number.
 */
/* static */ const char *VBoxNetDhcp::debugDhcpName(uint8_t uMsgType)
{
    switch (uMsgType)
    {
        case 0:                         return "MT_00";
        case RTNET_DHCP_MT_DISCOVER:    return "DISCOVER";
        case RTNET_DHCP_MT_OFFER:       return "OFFER";
        case RTNET_DHCP_MT_REQUEST:     return "REQUEST";
        case RTNET_DHCP_MT_DECLINE:     return "DECLINE";
        case RTNET_DHCP_MT_ACK:         return "ACK";
        case RTNET_DHCP_MT_NAC:         return "NAC";
        case RTNET_DHCP_MT_RELEASE:     return "RELEASE";
        case RTNET_DHCP_MT_INFORM:      return "INFORM";
        case 9:                         return "MT_09";
        case 10:                        return "MT_0a";
        case 11:                        return "MT_0b";
        case 12:                        return "MT_0c";
        case 13:                        return "MT_0d";
        case 14:                        return "MT_0e";
        case 15:                        return "MT_0f";
        case 16:                        return "MT_10";
        case 17:                        return "MT_11";
        case 18:                        return "MT_12";
        case 19:                        return "MT_13";
        case UINT8_MAX:                 return "MT_ff";
        default:                        return "UNKNOWN";
    }
}



/**
 *  Entry point.
 */
extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
{
    /*
     * Instantiate the DHCP server and hand it the options.
     */
    VBoxNetDhcp *pDhcp = new VBoxNetDhcp();
    if (!pDhcp)
    {
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: new VBoxNetDhcp failed!\n");
        return 1;
    }
    int rc = pDhcp->parseArgs(argc - 1, argv + 1);
    if (rc)
        return rc;

    /*
     * Try connect the server to the network.
     */
    rc = pDhcp->tryGoOnline();
    if (rc)
    {
        delete pDhcp;
        return rc;
    }

    /*
     * Process requests.
     */
    g_pDhcp = pDhcp;
    rc = pDhcp->run();
    g_pDhcp = NULL;
    delete pDhcp;

    return rc;
}


#ifndef VBOX_WITH_HARDENING

int main(int argc, char **argv, char **envp)
{
    int rc = RTR3InitAndSUPLib();
    if (RT_FAILURE(rc))
    {
        RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: RTR3InitAndSupLib failed, rc=%Rrc\n", rc);
        return 1;
    }

    return TrustedMain(argc, argv, envp);
}

# ifdef RT_OS_WINDOWS

static LRESULT CALLBACK WindowProc(HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    if(uMsg == WM_DESTROY)
    {
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc (hwnd, uMsg, wParam, lParam);
}

static LPCSTR g_WndClassName = "VBoxNetDHCPClass";

static DWORD WINAPI MsgThreadProc(__in  LPVOID lpParameter)
{
     HWND                 hwnd = 0;
     HINSTANCE hInstance = (HINSTANCE)GetModuleHandle (NULL);
     bool bExit = false;

     /* Register the Window Class. */
     WNDCLASS wc;
     wc.style         = 0;
     wc.lpfnWndProc   = WindowProc;
     wc.cbClsExtra    = 0;
     wc.cbWndExtra    = sizeof(void *);
     wc.hInstance     = hInstance;
     wc.hIcon         = NULL;
     wc.hCursor       = NULL;
     wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
     wc.lpszMenuName  = NULL;
     wc.lpszClassName = g_WndClassName;

     ATOM atomWindowClass = RegisterClass(&wc);

     if (atomWindowClass != 0)
     {
         /* Create the window. */
         hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
                 g_WndClassName, g_WndClassName,
                                                   WS_POPUPWINDOW,
                                                  -200, -200, 100, 100, NULL, NULL, hInstance, NULL);

         if (hwnd)
         {
             SetWindowPos(hwnd, HWND_TOPMOST, -200, -200, 0, 0,
                          SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);

             MSG msg;
             while (GetMessage(&msg, NULL, 0, 0))
             {
                 TranslateMessage(&msg);
                 DispatchMessage(&msg);
             }

             DestroyWindow (hwnd);

             bExit = true;
         }

         UnregisterClass (g_WndClassName, hInstance);
     }

     if(bExit)
     {
         /* no need any accuracy here, in anyway the DHCP server usually gets terminated with TerminateProcess */
         exit(0);
     }

     return 0;
}


/** (We don't want a console usually.) */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    NOREF(hInstance); NOREF(hPrevInstance); NOREF(lpCmdLine); NOREF(nCmdShow);

    HANDLE hThread = CreateThread(
      NULL, /*__in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes, */
      0, /*__in       SIZE_T dwStackSize, */
      MsgThreadProc, /*__in       LPTHREAD_START_ROUTINE lpStartAddress,*/
      NULL, /*__in_opt   LPVOID lpParameter,*/
      0, /*__in       DWORD dwCreationFlags,*/
      NULL /*__out_opt  LPDWORD lpThreadId*/
    );

    if(hThread != NULL)
        CloseHandle(hThread);

    return main(__argc, __argv, environ);
}
# endif /* RT_OS_WINDOWS */

#endif /* !VBOX_WITH_HARDENING */

