// ---------------------------------------------------------------------------------------
// xnetp.cpp
//
// Implementation of private XNet API's
//
// Copyright (C) Microsoft Corporation
// ---------------------------------------------------------------------------------------

#include "xnp.h"
#include "xnver.h"

// ---------------------------------------------------------------------------------------
// Trace Tags
// ---------------------------------------------------------------------------------------

DefineTag(NatPort, 0);
DefineTag(NatFlow, 0);

// ---------------------------------------------------------------------------------------
// Reading and Writing Configuration Sectors
// ---------------------------------------------------------------------------------------

HANDLE _WSAAPI_ XNetOpenConfigVolume()
{
    HANDLE              hVolume;
    OBJECT_ATTRIBUTES   oa;
    OBJECT_STRING       os;
    IO_STATUS_BLOCK     statusBlock;
    NTSTATUS            status;

    RtlInitObjectString(&os, OTEXT("\\Device\\Harddisk0\\partition0"));
    InitializeObjectAttributes(&oa, &os, OBJ_CASE_INSENSITIVE, NULL, NULL);

    status = NtOpenFile(&hVolume, SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, &oa, &statusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT);

    if (!NT_SUCCESS(status))
    {
        TraceSz1(Warning, "XNetOpenConfigVolume: NtOpenFile returned %08lX", status);
        hVolume = INVALID_HANDLE_VALUE;
    }

    return(hVolume);
}

BOOL _WSAAPI_ XNetLoadConfigSector(HANDLE hVolume, UINT iSector, BYTE * pbData, UINT cbData)
{
    BYTE                abSector[XBOX_HD_SECTOR_SIZE];
    PXBOX_CONFIG_SECTOR pxbcs = (PXBOX_CONFIG_SECTOR)abSector;
    LARGE_INTEGER       liOffset;
    IO_STATUS_BLOCK     statusBlock;
    NTSTATUS            status;

    Assert(hVolume != INVALID_HANDLE_VALUE);
    Assert(cbData == sizeof(pxbcs->Data));

    if (iSector >= XBOX_NUM_CONFIG_SECTORS)
    {
        TraceSz2(Warning, "XNetSaveConfigSector (%d): iSector must be between 0 and %d",
                 iSector, XBOX_NUM_CONFIG_SECTORS);
        return(FALSE);
    }

    liOffset.QuadPart = (XBOX_CONFIG_SECTOR_INDEX + iSector) * XBOX_HD_SECTOR_SIZE;

    status = NtReadFile(hVolume, 0, NULL, NULL, &statusBlock, abSector, sizeof(abSector), &liOffset);

    if (!NT_SUCCESS(status))
    {
        TraceSz2(Warning, "XNetLoadConfigSector (%d): NtReadFile returned %08lX", iSector, status);
        return(FALSE);
    }

    if (    pxbcs->SectorBeginSignature != XBOX_CONFIG_SECTOR_BEGIN_SIGNATURE
        ||  pxbcs->SectorEndSignature != XBOX_CONFIG_SECTOR_END_SIGNATURE
        ||  pxbcs->Version < XBOX_CONFIG_VERSION
        ||  pxbcs->SectorCount < XBOX_CONFIG_SECTOR_COUNT)
    {
        return(FALSE);
    }

    memcpy(pbData, pxbcs->Data, sizeof(pxbcs->Data));
    return(TRUE);
}

BOOL _WSAAPI_ XNetSaveConfigSector(HANDLE hVolume, UINT iSector, const BYTE * pbData, UINT cbData)
{
    BYTE                abSector[XBOX_HD_SECTOR_SIZE];
    PXBOX_CONFIG_SECTOR pxbcs = (PXBOX_CONFIG_SECTOR)abSector;
    LARGE_INTEGER       liOffset;
    IO_STATUS_BLOCK     statusBlock;
    NTSTATUS            status;

    Assert(hVolume != INVALID_HANDLE_VALUE);
    Assert(cbData == sizeof(pxbcs->Data));

    if (iSector >= XBOX_NUM_CONFIG_SECTORS)
    {
        TraceSz2(Warning, "XNetSaveConfigSector (%d): iSector must be between 0 and %d",
                 iSector, XBOX_NUM_CONFIG_SECTORS);
        return(FALSE);
    }

    liOffset.QuadPart = (XBOX_CONFIG_SECTOR_INDEX + iSector) * XBOX_HD_SECTOR_SIZE;

    pxbcs->SectorBeginSignature = XBOX_CONFIG_SECTOR_BEGIN_SIGNATURE;
    pxbcs->SectorEndSignature = XBOX_CONFIG_SECTOR_END_SIGNATURE;
    pxbcs->Version = XBOX_CONFIG_VERSION;
    pxbcs->SectorCount = XBOX_CONFIG_SECTOR_COUNT;

    memcpy(pxbcs->Data, pbData, sizeof(pxbcs->Data));
    
    // Recompute the checksum of the configuration sector

    pxbcs->Checksum = 0;
    pxbcs->Checksum = ~XConfigChecksum(pxbcs, sizeof(*pxbcs));

    // Write the config sector back to the disk

    status = NtWriteFile(hVolume, 0, NULL, NULL, &statusBlock, abSector, sizeof(abSector), &liOffset);

    if (!NT_SUCCESS(status))
    {
        TraceSz2(Warning, "XNetSaveConfigSector (%d): NtWriteFile returned %08lX", iSector, status);
        return(FALSE);
    }

    return(TRUE);
}

BOOL _WSAAPI_ XNetCloseConfigVolume(HANDLE hVolume)
{
    if (hVolume != INVALID_HANDLE_VALUE)
    {
        NtClose(hVolume);
    }

    return(TRUE);
}

BOOL _WSAAPI_ XNetLoadConfigParams(XNetConfigParams * pxncp)
{
    HANDLE hVolume = XNetOpenConfigVolume();

    if (hVolume != INVALID_HANDLE_VALUE)
    {
        pxncp->dwSigEnd = 0;

        XNetLoadConfigSector(hVolume, 0, (BYTE *)pxncp, sizeof(XNetConfigParams));

        if (pxncp->dwSigEnd != XNET_CONFIG_PARAMS_SIGEND)
        {
            memset(pxncp, 0, sizeof(pxncp));
            pxncp->dwSigEnd = XNET_CONFIG_PARAMS_SIGEND;
        }

        XNetCloseConfigVolume(hVolume);
    }

    return(TRUE);
}

BOOL _WSAAPI_ XNetSaveConfigParams(const XNetConfigParams * pxncp)
{
    BOOL   fResult = FALSE;
    HANDLE hVolume = XNetOpenConfigVolume();

    if (hVolume != INVALID_HANDLE_VALUE)
    {
        Assert(pxncp->dwSigEnd == XNET_CONFIG_PARAMS_SIGEND);

        fResult = XNetSaveConfigSector(hVolume, 0, (BYTE *)pxncp, sizeof(XNetConfigParams));

        XNetCloseConfigVolume(hVolume);
    }

    return(fResult);
}

// ---------------------------------------------------------------------------------------
// XoBase Support
// ---------------------------------------------------------------------------------------

#ifdef XNET_FEATURE_ONLINE

void CXoBase::XnSetXoBase(CXoBase * pXoBase)
{
    GetXn()->IpSetXoBase(pXoBase);
}

void CXoBase::XnLogon(IN_ADDR inaLogon, ULONGLONG * pqwUserId, WSAEVENT hEventLogon)
{
    GetXn()->IpLogon(CIpAddr(inaLogon.s_addr), pqwUserId, hEventLogon);
}

DWORD CXoBase::XnLogonGetStatus(SGADDR * psgaddr)
{
    return(GetXn()->IpLogonGetStatus(psgaddr));
}

BOOL CXoBase::XnLogonGetQFlags(UINT iUserId, ULONGLONG * pqwUserId, DWORD * pdwQFlags, DWORD * pdwSeqQFlags)
{
    return(GetXn()->IpLogonGetQFlags(iUserId, pqwUserId, pdwQFlags, pdwSeqQFlags));
}

BOOL CXoBase::XnLogonSetQFlags(UINT iUserId, DWORD dwQFlagsSet, DWORD dwQFlagsClr)
{
    return(GetXn()->IpLogonSetQFlags(iUserId, dwQFlagsSet, dwQFlagsClr));
}

BOOL CXoBase::XnLogonSetPState(UINT iUserId, DWORD dwPState, const XNKID * pxnkid, UINT cbData, BYTE * pbData)
{
    return(GetXn()->IpLogonSetPState(iUserId, dwPState, pxnkid, cbData, pbData));
}

void CXoBase::XnLogoff()
{
    GetXn()->IpLogoff();
}

void CXoBase::XnSetEventTimer(BYTE * pbEventTimer, WSAEVENT hEvent, DWORD dwTimeout)
{
    GetXn()->IpSetEventTimer((CEventTimer *)pbEventTimer, hEvent, dwTimeout);
}

void CXoBase::XnRaiseToDpc(BOOL fRaise)
{
    GetXn()->IpRaiseToDpc(fRaise);
}

#ifdef XNET_FEATURE_ASSERT

void * CXoBase::XnLeakAdd(CLeakInfo * pli, void * pv, UINT cb, ULONG tag)
{
    return(GetXn()->LeakAdd(pli, pv, cb, tag));
}

void * CXoBase::XnLeakDel(CLeakInfo * pli, void * pv)
{
    return(GetXn()->LeakDel(pli, pv));
}

void CXoBase::XnLeakTerm(CLeakInfo * pli)
{
    GetXn()->LeakTerm(pli);
}

#endif

#endif

// ---------------------------------------------------------------------------------------
// Simulated Network Address Translator
// ---------------------------------------------------------------------------------------

#ifdef XNET_FEATURE_WINDOWS

#include <pshpack1.h>

struct CDhcpHdr
{
    // Definitions -----------------------------------------------------------------------

    #define DHCP_OP_BOOTREQUEST         1
    #define DHCP_OP_BOOTREPLY           2

    #define DHCP_HWTYPE_ETHERNET        1
    #define DHCP_FLAGS_BROADCAST        0x0008
    #define DHCP_MAGIC_COOKIE           0x63538263

    #define DHCP_TYPE_DISCOVER          1
    #define DHCP_TYPE_OFFER             2
    #define DHCP_TYPE_REQUEST           3
    #define DHCP_TYPE_DECLINE           4
    #define DHCP_TYPE_ACK               5
    #define DHCP_TYPE_NAK               6
    #define DHCP_TYPE_RELEASE           7
    #define DHCP_TYPE_INFORM            8

    #define DHCP_OPT_PAD                0
    #define DHCP_OPT_SUBNET_MASK        1
    #define DHCP_OPT_ROUTERS            3
    #define DHCP_OPT_DNS_SERVERS        6
    #define DHCP_OPT_DOMAIN_NAME        15
    #define DHCP_OPT_REQUESTED_IPADDR   50
    #define DHCP_OPT_IPADDR_LEASE_TIME  51
    #define DHCP_OPT_FIELD_OVERLOAD     52
    #define DHCP_OPT_MESSAGE_TYPE       53
    #define DHCP_OPT_SERVERID           54
    #define DHCP_OPT_PARAM_REQUEST_LIST 55
    #define DHCP_OPT_T1_INTERVAL        58
    #define DHCP_OPT_T2_INTERVAL        59
    #define DHCP_OPT_CLIENTID           61
    #define DHCP_OPT_END                255

    #define DHCP_OVERLOAD_FILE          1
    #define DHCP_OVERLOAD_SERVER        2

    // Functions -------------------------------------------------------------------------

    BOOL        GetOpt(UINT cbLen, BYTE bOpt, UINT cbOpt, BOOL fVariable, BYTE ** ppbOpt, UINT * pcbOpt);
    static BOOL GetOpt(BYTE * pb, UINT cb, BYTE bOpt, UINT cbOpt, BOOL fVar, BYTE ** ppbOpt, UINT * pcbOpt, BYTE * pbOver);

    // Data ------------------------------------------------------------------------------

    BYTE        _bOp;               // DHCP_OP_*
    BYTE        _bHwType;           // DHCP_HWTYPE_*
    BYTE        _bHwLen;            // hardware address length
    BYTE        _bHops;             // relay hops
    DWORD       _dwXid;             // random transaction identifier
    WORD        _wSecs;             // seconds since address acquisition process began
    WORD        _wFlags;            // DHCP_FLAGS_*
    CIpAddr     _ipaC;              // IP address requested by client
    CIpAddr     _ipaY;              // IP address assigned to client
    CIpAddr     _ipaS;              // IP address of DHCP server
    CIpAddr     _ipaG;              // IP address of gateway in trans-router booting
    CEnetAddr   _ea;                // Ethernet Address of client
    BYTE        _abHwPad[10];       // Pad hardware address to full 16 bytes
    char        _achServer[64];     // Null-terminated server hostname
    char        _achFile[128];      // Null-terminated name of bootfile
    DWORD       _dwMagicCookie;     // DHCP_MAGIC_COOKIE

};

#include <poppack.h>

BOOL CDhcpHdr::GetOpt(UINT cbLen, BYTE bOpt, UINT cbOpt, BOOL fVar, BYTE ** ppbOpt, UINT * pcbOpt)
{
    BYTE bOver = 0;

    if (GetOpt((BYTE *)(this + 1), cbLen - sizeof(CDhcpHdr), bOpt, cbOpt, fVar, ppbOpt, pcbOpt, &bOver))
    {
        return(TRUE);
    }

    if (bOver & DHCP_OVERLOAD_FILE)
    {
        if (GetOpt((BYTE *)_achFile, sizeof(_achFile), bOpt, cbOpt, fVar, ppbOpt, pcbOpt, NULL))
        {
            return(TRUE);
        }
    }

    if (bOver & DHCP_OVERLOAD_SERVER)
    {
        if (GetOpt((BYTE *)_achServer, sizeof(_achServer), bOpt, cbOpt, fVar, ppbOpt, pcbOpt, NULL))
        {
            return(TRUE);
        }
    }

    return(FALSE);
}

BOOL CDhcpHdr::GetOpt(BYTE * pb, UINT cb, BYTE bOpt, UINT cbOpt, BOOL fVar, BYTE ** ppbOpt, UINT * pcbOpt, BYTE * pbOver)
{
    BYTE b1, b2;

    while (cb > 0)
    {
        b1  = *pb++;
        cb -= 1;

        if (b1 == DHCP_OPT_END)
            break;

        if (b1 == DHCP_OPT_PAD)
            continue;

        if (cb == 0)
        {
            TraceSz1(Warning, "CDhcpHdr::GetOpt(%d) - Premature end of option list", bOpt);
            break;
        }

        b2  = *pb++;
        cb -= 1;

        if (b2 == 0 || b2 > cb)
        {
            TraceSz3(Warning, "CDhcpHdr::GetOpt(%d) - Option length (%d) is invalid (cb %d)", bOpt, b2, cb);
            break;
        }

        if (b1 == DHCP_OPT_FIELD_OVERLOAD && pbOver && *pbOver == 0 && b2 == 1)
        {
            *pbOver = *pb;
        }

        if (b1 == bOpt)
        {
            if (    (!fVar && b2 != cbOpt)
                ||  ( fVar && (b2 < cbOpt || (b2 % cbOpt) != 0)))
            {
                TraceSz4(Warning, "CDhcpHdr::GetOpt(%d) - Option length (%d) is invalid (%d%s)",
                         bOpt, b2, cbOpt, fVar ? " variable" : "");
                break;
            }

            *ppbOpt = pb;

            if (pcbOpt)
            {
                *pcbOpt = b2;
            }

            return(TRUE);
        }

        pb += b2;
        cb -= b2;
    }

    return(FALSE);
}

class CXnNat
{
    // Definitions -----------------------------------------------------------------------

    struct CDhcpLease
    {
        CEnetAddr       _ea;                // Ethernet address of client
        CIpAddr         _ipa;               // IP address assigned to client
        FILETIME        _ftExpire;          // Expiration time of the lease
    };

    struct CNatFilt
    {
        CIpAddr         _ipaOuter;          // IP address of server on outer lan
        CIpPort         _ipportOuter;       // IP port of server on outer lan
        DWORD           _dwTick;            // Tick of last packet activity through this filter
    };

    struct CNatPort
    {
        LIST_ENTRY      _leHash;            // Link into hash table bucket
        LIST_ENTRY      _leLru;             // Link into LRU list
        CIpAddr         _ipaInner;          // IP address of client on inner lan
        CIpPort         _ipportInner;       // IP port of client on inner lan
        CIpPort         _ipportNat;         // IP port assigned by NAT
        CIpAddr         _ipaOuter;          // IP address of server on outer lan (aggressive) 
        CIpPort         _ipportOuter;       // IP port of server on outer lan (aggressive)
        CNatFilt *      _pNatFilt;          // Vector of sorted CNatFilt structures
        UINT            _cNatFilt;          // Count of CNatFilt structures in _pNatFilt in use
        UINT            _cNatFiltAlloc;     // Count of CNatFilt structures in _pNatFilt
        DWORD           _dwTick;            // Tick of last packet activity through this mapping
    };

    class CXnIpNat : public CXnIp
    {

    public:

        HAL_DECLARE_NEW_DELETE(CXnIpNat)

        BOOL    Init(CXnNat * pXnNat, XNetStartupParams * pxnsp, XNetConfigParams * pxncp, char * pszXbox);
        void    Stop();
        void    Term();
        void    UdpRecv(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen);
        void    TcpRecv(CPacket * ppkt, CIpHdr * pIpHdr, CTcpHdr * pTcpHdr, UINT cbHdrLen, UINT cbLen);
        void    SockReset(CIpAddr ipa) {}
        BOOL    IsGateway(CIpPort ipport);
        void    IpXmitIp(CPacket * ppkt) { CXnIp::IpXmitIp(ppkt); }

        CIpAddr GetIpa()                { return(_ipa); }
        CIpAddr GetIpaMask()            { return(_ipaMask); }
        CIpAddr GetIpaDns(UINT iDns)    { return(iDns < _options._dnsServerCount ? _options._dnsServers[iDns] : 0); }

    private:

        CXnNat *    _pXnNat;

    };

public:

    HAL_DECLARE_NEW_DELETE(CXnNat)

    BOOL        Init(NATCREATE * pNatCreate);
    void        Term();
    void        Recv(CXnIpNat * pXnIpNat, CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen);
    void        RecvDhcp(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen);
    void        RecvInner(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen);
    void        RecvOuter(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen);
    BOOL        IsGateway(CIpPort ipport, CXnIpNat * pXnIpNat);
    CNatPort *  NatPortLookup(CIpAddr ipaInner, CIpPort ipportInner, CIpAddr ipaOuter, CIpPort ipportOuter);
    CNatPort *  NatPortLookup(CIpPort ipportNat);
    BOOL        NatPortExpire(CNatPort * pNatPort);
    void        NatPortFree(CNatPort * pNatPort);
    CNatFilt *  NatFiltLookup(CNatPort * pNatPort, CIpAddr ipaOuter, CIpPort ipportOuter, BOOL fAlloc);

private:

    CXnIpNat *      _pXnIpNatInner;         // Inner network stack
    CXnIpNat *      _pXnIpNatOuter;         // Outer network stack
    DWORD           _dwIpaBase;             // Base IP address (host order) of DHCP server
    DWORD           _dwIpaLast;             // Last IP address (host order) of DHCP server
    DWORD           _dwIpaNext;             // Next IP address (host order) of DHCP server
    DWORD           _dwLeaseTime;           // Lease time in seconds for DHCP server
    CDhcpLease *    _pDhcpLease;            // Vector of CDhcpLease structures
    UINT            _cDhcpLease;            // Count of CDhcpLease structures in use
    UINT            _cDhcpLeaseAlloc;       // Count of CDhcpLease structures allocated
    UINT            _iAssign;               // See NAT_ASSIGN_*
    UINT            _iFilter;               // See NAT_FILTER_*
    UINT            _iTimeout;              // NAT timeout interval in ticks
    LIST_ENTRY *    _pleHash;               // Vector of hash buckets of CNatPort structures
    UINT            _cBucket;               // Number of buckets in the hash table
    LIST_ENTRY      _leLru;                 // CNatPort structures in LRU order
    CNatPort **     _ppNatPort;             // Mapping from ipportNat to CNatPort
    UINT            _iNatPortBase;          // Base ipportNat to assign
    UINT            _iNatPortLast;          // Last ipportNat to assign
    UINT            _iNatPortNext;          // Next ipportNat to assign
    UINT            _cNatPortFree;          // Count of free ipportNat
    BOOL            _fInitialized;          // TRUE when CXnNat::Init has completed

};

BOOL CXnNat::CXnIpNat::Init(CXnNat * pXnNat, XNetStartupParams * pxnsp, XNetConfigParams * pxncp, char * pszXbox)
{
    _pXnNat = pXnNat;

    XNetInitParams xnip;
    xnip.pxnp = (XNetParams *)pxnsp;
    xnip.pszXbox = pszXbox;
    HalQueryTsc(&xnip.liTime);

    NTSTATUS status = IpInit(&xnip);

    if (!NT_SUCCESS(status))
    {
        TraceSz1(Warning, "CXnNat::CXnIpNat::Init - IpInit failed (%08lX)", status);
        return(FALSE);
    }

    cfgFlags |= XNET_STARTUP_BYPASS_SECURITY;

    INT err = IpConfig(pxncp, XNET_CONFIG_NORMAL);

    if (err != 0)
    {
        TraceSz1(Warning, "CXnNat::CXnIpNat::Init - IpConfig failed (%08lX)", err);
        return(FALSE);
    }

    IpStart();

    return(TRUE);    
}

void CXnNat::CXnIpNat::Stop()
{
    if (TestInitFlag(INITF_HAL))
    {
        RaiseToDpc();
        IpStop();
    }
}

void CXnNat::CXnIpNat::Term()
{
    if (TestInitFlag(INITF_HAL))
    {
        RaiseToDpc();
        IpTerm();
    }

    _pXnNat = NULL;
}

void CXnNat::CXnIpNat::UdpRecv(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen)
{
    _pXnNat->Recv(this, ppkt, pIpHdr, pUdpHdr, cbLen);
}

void CXnNat::CXnIpNat::TcpRecv(CPacket * ppkt, CIpHdr * pIpHdr, CTcpHdr * pTcpHdr, UINT cbHdrLen, UINT cbLen)
{
    _pXnNat->Recv(this, ppkt, pIpHdr, (CUdpHdr *)pTcpHdr, cbLen);
}

BOOL CXnNat::CXnIpNat::IsGateway(CIpPort ipport)
{
    return(_pXnNat->IsGateway(ipport, this));
}

BOOL CXnNat::Init(NATCREATE * pNatCreate)
{
    _dwIpaBase     = NTOHL(pNatCreate->_inaBase.s_addr);
    _dwIpaLast     = NTOHL(pNatCreate->_inaLast.s_addr);
    _dwIpaNext     = _dwIpaBase;
    _dwLeaseTime   = pNatCreate->_dwLeaseTime;
    _iAssign       = pNatCreate->_iAssign;
    _iFilter       = pNatCreate->_iFilter;
    _iTimeout      = (pNatCreate->_iTimeout ? pNatCreate->_iTimeout : 60) * TICKS_PER_SECOND;
    _iNatPortBase  = pNatCreate->_iNatPortBase ? pNatCreate->_iNatPortBase : 1000;
    _iNatPortLast  = _iNatPortBase + (pNatCreate->_cNatPort ? pNatCreate->_cNatPort : 1000) - 1;
    _iNatPortNext  = _iNatPortBase;

    if (_iAssign < NAT_ASSIGN_MINIMAL && _iAssign > NAT_ASSIGN_AGGRESSIVE)
        _iAssign = NAT_ASSIGN_MINIMAL;
    if (_iFilter < NAT_FILTER_NONE || _iFilter > NAT_FILTER_ADDRESS_PORT)
        _iFilter = NAT_FILTER_NONE;

    if (_iNatPortLast > 0xFFFF)
        _iNatPortLast = 0xFFFF;

    _cNatPortFree  = _iNatPortLast - _iNatPortBase + 1;
    _cBucket       = _cNatPortFree * 160 / 100;

    static DWORD s_adwPrimes[] = { 37,59,89,139,227,359,577,929,1499,2423,3919,6337,10253,16573,
        26821,43391,70207,113591,183797,297377,481171,778541,1259701,2038217,3297913,5336129,
        8633983,13970093,22604069,36574151,59178199,95752333,154930511,250682837,405613333,
        656296153,1061909479,1718205583,2780115059,0xFFFFFFFF};

    DWORD * pdwPrime = s_adwPrimes;
    for (; _cBucket < *pdwPrime; pdwPrime++) ;
    _cBucket = *pdwPrime;

    _pleHash       = (LIST_ENTRY *)HalAllocZ(_cBucket * sizeof(LIST_ENTRY), PTAG_CNatPort);
    _ppNatPort     = (CNatPort **)HalAllocZ(_cNatPortFree * sizeof(CNatPort *), PTAG_CNatPort);
    _pXnIpNatInner = new CXnIpNat;
    _pXnIpNatOuter = new CXnIpNat;

    if (_pleHash == NULL || _ppNatPort == NULL || _pXnIpNatInner == NULL || _pXnIpNatOuter == NULL)
    {
        TraceSz(Warning, "CXnNat::Init - Out of memory");
        return(FALSE);
    }

    InitializeListHead(&_leLru);

    LIST_ENTRY *    ple     = _pleHash;
    UINT            cBucket = _cBucket;

    for (; cBucket > 0; --cBucket, ++ple)
    {
        InitializeListHead(ple);
    }

    if (!_pXnIpNatInner->Init(this, pNatCreate->_pxnsp, &pNatCreate->_xncpInner, pNatCreate->_achXboxInner))
    {
        return(FALSE);
    }

    if (!_pXnIpNatOuter->Init(this, pNatCreate->_pxnsp, &pNatCreate->_xncpOuter, pNatCreate->_achXboxOuter))
    {
        return(FALSE);
    }

    _fInitialized = TRUE;

    return(TRUE);
}

void CXnNat::Term()
{
    if (_pXnIpNatInner)
    {
        _pXnIpNatInner->Stop();
    }

    if (_pXnIpNatOuter)
    {
        _pXnIpNatOuter->Stop();
    }

    if (_pXnIpNatInner)
    {
        _pXnIpNatInner->Term();
        delete _pXnIpNatInner;
        _pXnIpNatInner = NULL;
    }

    if (_pXnIpNatOuter)
    {
        _pXnIpNatOuter->Term();
        delete _pXnIpNatOuter;
        _pXnIpNatOuter = NULL;
    }

    if (!IsListNull(&_leLru))
    {
        while (!IsListEmpty(&_leLru))
        {
            CNatPort * pNatPort = (CNatPort *)((BYTE *)_leLru.Flink - offsetof(CNatPort, _leLru));
            NatPortFree(pNatPort);
        }
    }

    if (_ppNatPort)
    {
        HalFree(_ppNatPort);
    }

    if (_pDhcpLease)
    {
        HalFree(_pDhcpLease);
    }
}

void CXnNat::Recv(CXnIpNat * pXnIpNat, CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen)
{
    if (!_fInitialized)
        return;

    if (pXnIpNat == _pXnIpNatOuter)
        RecvOuter(ppkt, pIpHdr, pUdpHdr, cbLen);
    else if (pIpHdr->_bProtocol == IPPROTOCOL_UDP && pUdpHdr->_ipportDst == DHCP_SERVER_PORT)
        RecvDhcp(ppkt, pIpHdr, pUdpHdr, cbLen);
    else
        RecvInner(ppkt, pIpHdr, pUdpHdr, cbLen);
}

void CXnNat::RecvDhcp(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen)
{
    if (cbLen < sizeof(CDhcpHdr))
    {
        TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - DHCP payload is too small");
        return;
    }

    CDhcpHdr * pDhcpHdr = (CDhcpHdr *)(pUdpHdr + 1);

    if (    pDhcpHdr->_bOp != DHCP_OP_BOOTREQUEST
        ||  pDhcpHdr->_bHwType != DHCP_HWTYPE_ETHERNET
        ||  pDhcpHdr->_bHwLen != sizeof(CEnetAddr)
        ||  pDhcpHdr->_dwMagicCookie != DHCP_MAGIC_COOKIE)
    {
        TraceSz4(pktWarn, "[DISCARD] CXnNat::RecvDhcp - DHCP request is invalid (%d,%d,%d,%d)",
                 (pDhcpHdr->_bOp != DHCP_OP_BOOTREQUEST),
                 (pDhcpHdr->_bHwType != DHCP_HWTYPE_ETHERNET),
                 (pDhcpHdr->_bHwLen != sizeof(CEnetAddr)),
                 (pDhcpHdr->_dwMagicCookie != DHCP_MAGIC_COOKIE));
        return;
    }

    BYTE * pbOpt;
    UINT   cbOpt;
    BYTE   bType;

    if (!pDhcpHdr->GetOpt(cbLen, DHCP_OPT_MESSAGE_TYPE, 1, FALSE, &pbOpt, NULL))
    {
        TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - DHCP_OPT_MESSAGE_TYPE not found");
        return;
    }

    bType = *pbOpt;

    if (bType != DHCP_TYPE_DISCOVER && bType != DHCP_TYPE_REQUEST)
    {
        TraceSz1(pktWarn, "[DISCARD] CXnNat::RecvDhcp - DHCP_OPT_MESSAGE_TYPE (%d) not supported", bType);
        return;
    }

    if (_pXnIpNatOuter->GetIpa() == 0)
    {
        TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - NAT hasn't acquired outer IP address yet");
        return;
    }

    CDhcpLease * pDhcpLease = _pDhcpLease;
    UINT         cDhcpLease = _cDhcpLease;

    for (; cDhcpLease > 0; ++pDhcpLease, --cDhcpLease)
    {
        if (pDhcpLease->_ea.IsEqual(pDhcpHdr->_ea))
            break;
    }

    if (cDhcpLease == 0)
    {
        if (bType != DHCP_TYPE_DISCOVER)
        {
            TraceSz1(pktWarn, "[DISCARD] CXnNat::RecvDhcp - No lease found for %s", pDhcpHdr->_ea.Str());
            return;
        }

        for (; _dwIpaNext <= _dwIpaLast; ++_dwIpaNext)
        {
            if (CIpAddr(HTONL(_dwIpaNext)).IsValidAddr())
                break;
        }

        if (_dwIpaNext > _dwIpaLast)
        {
            TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - Out of IP addresses");
            return;
        }

        if (_cDhcpLease == _cDhcpLeaseAlloc)
        {
            UINT         cDhcpLeaseAlloc = _cDhcpLeaseAlloc + 64;
            CDhcpLease * pDhcpLease      = (CDhcpLease *)HalAllocZ(cDhcpLeaseAlloc * sizeof(CDhcpLease), PTAG_CDhcpLease);

            if (pDhcpLease == NULL)
            {
                TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - Out of memory allocating CDhcpLease vector");
                return;
            }

            if (_cDhcpLease > 0)
            {
                memcpy(pDhcpLease, _pDhcpLease, _cDhcpLease * sizeof(CDhcpLease));
                HalFree(_pDhcpLease);
            }

            _pDhcpLease      = pDhcpLease;
            _cDhcpLeaseAlloc = cDhcpLeaseAlloc;
        }

        pDhcpLease       = &_pDhcpLease[_cDhcpLease];
        pDhcpLease->_ea  = pDhcpHdr->_ea;
        pDhcpLease->_ipa = HTONL(_dwIpaNext);
        _dwIpaNext      += 1;
        _cDhcpLease     += 1;
    }

    if (bType == DHCP_TYPE_REQUEST)
    {
        if (!pDhcpHdr->GetOpt(cbLen, DHCP_OPT_REQUESTED_IPADDR, sizeof(CIpAddr), FALSE, &pbOpt, NULL))
        {
            TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - REQUESTED_IPADDR not supplied");
            return;
        }

        if (*(CIpAddr *)pbOpt != pDhcpLease->_ipa)
        {
            TraceSz2(pktWarn, "[DISCARD] CXnNat::RecvDhcp - REQUESTED_IPADDR is %s, expecting %s",
                     *(CIpAddr *)pbOpt, pDhcpLease->_ipa);
            return;
        }
    }

    CPacket * ppktReply = _pXnIpNatInner->PacketAlloc(PTAG_CDhcpPacket,
        PKTF_TYPE_UDP|PKTF_XMIT_INSECURE|PKTF_POOLALLOC, sizeof(CDhcpHdr) + 128);

    if (ppktReply == NULL)
    {
        TraceSz(pktWarn, "[DISCARD] CXnNat::RecvDhcp - Out of memory allocating CDhcpPacket");
        return;
    }

    CIpHdr *    pIpHdrReply     = ppktReply->GetIpHdr();
    CUdpHdr *   pUdpHdrReply    = (CUdpHdr *)(pIpHdrReply + 1);
    CDhcpHdr *  pDhcpHdrReply   = (CDhcpHdr *)(pUdpHdrReply + 1);
    BYTE *      pbOptReply      = (BYTE *)(pDhcpHdrReply + 1);

    pUdpHdrReply->_ipportSrc = DHCP_SERVER_PORT;
    pUdpHdrReply->_ipportDst = DHCP_CLIENT_PORT;
    
    memset(pDhcpHdrReply, 0, sizeof(CDhcpHdr));
    pDhcpHdrReply->_bOp = DHCP_OP_BOOTREPLY;
    pDhcpHdrReply->_bHwType = DHCP_HWTYPE_ETHERNET;
    pDhcpHdrReply->_bHwLen = sizeof(CEnetAddr);
    pDhcpHdrReply->_dwXid = pDhcpHdr->_dwXid;
    pDhcpHdrReply->_wFlags = pDhcpHdr->_wFlags;
    pDhcpHdrReply->_ipaY = pDhcpLease->_ipa;
    pDhcpHdrReply->_ea = pDhcpLease->_ea;
    pDhcpHdrReply->_dwMagicCookie = DHCP_MAGIC_COOKIE;

    *pbOptReply++ = DHCP_OPT_MESSAGE_TYPE;
    *pbOptReply++ = 1;
    *pbOptReply++ = bType == DHCP_TYPE_DISCOVER ? DHCP_TYPE_OFFER : DHCP_TYPE_ACK;

    *pbOptReply++ = DHCP_OPT_IPADDR_LEASE_TIME;
    *pbOptReply++ = sizeof(DWORD);
    *(DWORD *)pbOptReply = HTONL(_dwLeaseTime);
    pbOptReply += sizeof(DWORD);

    *pbOptReply++ = DHCP_OPT_SERVERID;
    *pbOptReply++ = sizeof(CIpAddr);
    *(CIpAddr *)pbOptReply = _pXnIpNatInner->GetIpa();
    pbOptReply += sizeof(CIpAddr);

    if (pDhcpHdr->GetOpt(cbLen, DHCP_OPT_PARAM_REQUEST_LIST, 1, TRUE, &pbOpt, &cbOpt))
    {
        for (; cbOpt > 0; ++pbOpt, --cbOpt)
        {
            switch (*pbOpt)
            {
                case DHCP_OPT_SUBNET_MASK:
                    *pbOptReply++ = DHCP_OPT_SUBNET_MASK;
                    *pbOptReply++ = sizeof(CIpAddr);
                    *(CIpAddr *)pbOptReply = _pXnIpNatInner->GetIpaMask();
                    pbOptReply += sizeof(CIpAddr);
                    break;

                case DHCP_OPT_ROUTERS:
                    *pbOptReply++ = DHCP_OPT_ROUTERS;
                    *pbOptReply++ = sizeof(CIpAddr);
                    *(CIpAddr *)pbOptReply = _pXnIpNatInner->GetIpa();
                    pbOptReply += sizeof(CIpAddr);
                    break;

                case DHCP_OPT_DNS_SERVERS:
                {
                    CIpAddr *   pipa = (CIpAddr *)(pbOptReply + 2);
                    UINT        iDns;

                    for (iDns = 0; iDns < MAX_DEFAULT_DNSSERVERS; ++iDns)
                    {
                        CIpAddr ipaDns = _pXnIpNatOuter->GetIpaDns(iDns);

                        if (ipaDns == 0)
                            break;

                        *pipa++ = ipaDns;
                    }

                    if (iDns > 0)
                    {
                        *pbOptReply++ = DHCP_OPT_DNS_SERVERS;
                        *pbOptReply++ = iDns * sizeof(CIpAddr);
                        pbOptReply += iDns * sizeof(CIpAddr);
                    }
                    break;
                }
            }
        }
    }

    *pbOptReply++ = DHCP_OPT_END;

    ppktReply->SetCb(pbOptReply - (BYTE *)pIpHdrReply);
    pUdpHdrReply->_wLen = NTOHS(pbOptReply - (BYTE *)pUdpHdrReply);

    _pXnIpNatInner->IpFillAndXmit(ppktReply, pIpHdr->_ipaSrc ? pIpHdr->_ipaSrc : IPADDR_BROADCAST, IPPROTOCOL_UDP);
}

void CXnNat::RecvInner(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen)
{
    if (    ppkt->TestFlags(PKTF_RECV_BROADCAST|PKTF_RECV_LOOPBACK)
        ||  ppkt->GetHdrOptLen() > 0
        ||  pIpHdr->_ipaDst == _pXnIpNatInner->GetIpa())
        return;

    CIpAddr     ipaInner    = pIpHdr->_ipaSrc;
    CIpPort     ipportInner = pUdpHdr->_ipportSrc;
    CIpAddr     ipaOuter    = pIpHdr->_ipaDst;
    CIpPort     ipportOuter = pUdpHdr->_ipportDst;
    DWORD       dwTick      = _pXnIpNatInner->TimerTick();

    CNatPort * pNatPort = NatPortLookup(ipaInner, ipportInner, ipaOuter, ipportOuter);

    if (pNatPort == NULL)
    {
        TraceSz5(Warning, "[DISCARD] Cannot allocate CNatPort for outbound %s [%s:%d %s:%d]",
                 pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                 ipaInner.Str(), NTOHS(ipportInner), ipaOuter.Str(), NTOHS(ipportOuter));
        return;
    }

    pNatPort->_dwTick = dwTick;

    if (_iFilter)
    {
        CNatFilt * pNatFilt = NatFiltLookup(pNatPort, ipaOuter, ipportOuter, TRUE);

        if (pNatFilt == NULL)
        {
            TraceSz5(Warning, "[DISCARD] Out of memory allocating CNatFilt for outbound %s [%s:%d %s:%d]",
                     pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                     ipaInner.Str(), NTOHS(ipportInner), ipaOuter.Str(), NTOHS(ipportOuter));
            return;
        }

        pNatFilt->_dwTick = dwTick;
    }

    // Copy the packet, change source address and port, and forward to _pXnIpNatOuter

    CPacket * ppktNat = _pXnIpNatOuter->PacketAlloc(PTAG_CNatPacket,
        PKTF_TYPE_IP|PKTF_POOLALLOC|PKTF_XMIT_INSECURE, ppkt->GetCb() - sizeof(CIpHdr));

    if (ppktNat == NULL)
    {
        TraceSz5(pktWarn, "[DISCARD] Out of memory allocating CNatPacket for outbound %s [%s:%d %s:%d]",
                 pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                 ipaInner.Str(), NTOHS(ipportInner), ipaOuter.Str(), NTOHS(ipportOuter));
        return;
    }

    ppktNat->SetType((WORD)(pIpHdr->_bProtocol == IPPROTOCOL_UDP ? PKTF_TYPE_UDP : PKTF_TYPE_TCP));

    CIpHdr *  pIpHdrNat  = ppktNat->GetIpHdr();
    CUdpHdr * pUdpHdrNat = (CUdpHdr *)(pIpHdrNat + 1);

    Assert(ppkt->GetCb() == ppktNat->GetCb());
    memcpy(pIpHdrNat, pIpHdr, ppkt->GetCb());

    pIpHdrNat->_ipaSrc = _pXnIpNatOuter->GetIpa();
    pUdpHdrNat->_ipportSrc = pNatPort->_ipportNat;

    TraceSz10(NatFlow, ">> %s [%s:%d %s:%d] -> [%s:%d %s:%d] (+%d)",
              pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
              pIpHdr->_ipaSrc.Str(), NTOHS(pUdpHdr->_ipportSrc),
              pIpHdr->_ipaDst.Str(), NTOHS(pUdpHdr->_ipportDst),
              pIpHdrNat->_ipaSrc.Str(), NTOHS(pUdpHdrNat->_ipportSrc),
              pIpHdrNat->_ipaDst.Str(), NTOHS(pUdpHdrNat->_ipportDst),
              cbLen);

    _pXnIpNatOuter->IpXmit(ppktNat);
}

void CXnNat::RecvOuter(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen)
{
    if (ppkt->TestFlags(PKTF_RECV_BROADCAST|PKTF_RECV_LOOPBACK) || ppkt->GetHdrOptLen() > 0)
        return;

    CIpAddr     ipaOuter    = pIpHdr->_ipaSrc;
    CIpPort     ipportOuter = pUdpHdr->_ipportSrc;
    CIpAddr     ipaNat      = pIpHdr->_ipaDst;
    CIpPort     ipportNat   = pUdpHdr->_ipportDst;
    DWORD       dwTick      = _pXnIpNatInner->TimerTick();

    CNatPort * pNatPort = NatPortLookup(ipportNat);

    if (pNatPort == NULL)
    {
        TraceSz5(NatPort, "!! No port mapping found for inbound %s [%s:%d %s:%d]",
                 pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                 ipaOuter.Str(), NTOHS(ipportOuter), ipaNat.Str(), NTOHS(ipportNat));
        return;
    }

    pNatPort->_dwTick = dwTick;
    RemoveEntryList(&pNatPort->_leLru);
    InsertTailList(&_leLru, &pNatPort->_leLru);

    if (_iFilter)
    {
        CNatFilt * pNatFilt = NatFiltLookup(pNatPort, ipaOuter, ipportOuter, FALSE);

        if (pNatFilt == NULL)
        {
            TraceSz10(NatPort, "!! %s filter discarding inbound %s [%s:%d %s:%d] -> [%s:%d %s:%d]",
                      _iFilter == NAT_FILTER_ADDRESS ? "Addr" : _iFilter == NAT_FILTER_PORT ? "Port" : "AddrPort",
                      pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                      ipaOuter.Str(), NTOHS(ipportOuter),
                      ipaNat.Str(), NTOHS(ipportNat),
                      ipaOuter.Str(), NTOHS(ipportOuter),
                      pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner));
            return;
        }

        pNatFilt->_dwTick = dwTick;
    }

    // Copy the packet, change source address and port, and forward to _pXnIpNatOuter

    CPacket * ppktNat = _pXnIpNatInner->PacketAlloc(PTAG_CNatPacket,
        PKTF_TYPE_IP|PKTF_POOLALLOC|PKTF_XMIT_INSECURE, ppkt->GetCb() - sizeof(CIpHdr));

    if (ppktNat == NULL)
    {
        TraceSz5(pktWarn, "[DISCARD] Out of memory allocating CNatPacket for inbound %s [%s:%d %s:%d]",
                 pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
                 ipaOuter.Str(), NTOHS(ipportOuter), ipaNat.Str(), NTOHS(ipportNat));
        return;
    }

    ppktNat->SetType((WORD)(pIpHdr->_bProtocol == IPPROTOCOL_UDP ? PKTF_TYPE_UDP : PKTF_TYPE_TCP));

    CIpHdr *  pIpHdrNat  = ppktNat->GetIpHdr();
    CUdpHdr * pUdpHdrNat = (CUdpHdr *)(pIpHdrNat + 1);

    Assert(ppkt->GetCb() == ppktNat->GetCb());
    memcpy(pIpHdrNat, pIpHdr, ppkt->GetCb());

    pIpHdrNat->_ipaDst = pNatPort->_ipaInner;
    pUdpHdrNat->_ipportDst = pNatPort->_ipportInner;

    TraceSz10(NatFlow, "<< %s [%s:%d %s:%d] -> [%s:%d %s:%d] (+%d)",
              pIpHdr->_bProtocol == IPPROTOCOL_UDP ? "UDP" : "TCP",
              pIpHdr->_ipaSrc.Str(), NTOHS(pUdpHdr->_ipportSrc),
              pIpHdr->_ipaDst.Str(), NTOHS(pUdpHdr->_ipportDst),
              pIpHdrNat->_ipaSrc.Str(), NTOHS(pUdpHdrNat->_ipportSrc),
              pIpHdrNat->_ipaDst.Str(), NTOHS(pUdpHdrNat->_ipportDst),
              cbLen);

    _pXnIpNatInner->IpXmitIp(ppktNat);
}

CXnNat::CNatPort * CXnNat::NatPortLookup(CIpAddr ipaInner, CIpPort ipportInner, CIpAddr ipaOuter, CIpPort ipportOuter)
{
    CNatPort * pNatPort;
    
    while (!IsListEmpty(&_leLru))
    {
        pNatPort = (CNatPort *)((BYTE *)_leLru.Flink - offsetof(CNatPort, _leLru));

        if (!NatPortExpire(pNatPort))
            break;
    }

    DWORD dwHash = ipaInner ^ (ipportInner << 3);

    if (_iAssign == NAT_ASSIGN_AGGRESSIVE)
    {
        dwHash ^= (ipaOuter << 13);
        dwHash ^= (ipaOuter >> 19);
        dwHash ^= (ipportOuter << 7);
    }
    else
    {
        ipaOuter = 0;
        ipportOuter = 0;
    }

    LIST_ENTRY * pleHead = &_pleHash[dwHash % _cBucket];

    for (LIST_ENTRY * ple = pleHead->Flink; ple != pleHead; ple = ple->Flink)
    {
        pNatPort = (CNatPort *)ple;

        if (    pNatPort->_ipaInner == ipaInner
            &&  pNatPort->_ipportInner == ipportInner
            &&  pNatPort->_ipaOuter == ipaOuter
            &&  pNatPort->_ipportOuter == ipportOuter)
        {
            return(pNatPort);
        }
    }

    if (_cNatPortFree == 0)
    {
        return(NULL);
    }

    pNatPort = (CNatPort *)HalAllocZ(sizeof(CNatPort), PTAG_CNatPort);

    if (pNatPort != NULL)
    {
        while (1)
        {
            if (_iNatPortNext > _iNatPortLast)
                _iNatPortNext = _iNatPortBase;

            if (_ppNatPort[_iNatPortNext - _iNatPortBase] == NULL)
                break;

            _iNatPortNext += 1;
        }

        pNatPort->_ipaInner     = ipaInner;
        pNatPort->_ipportInner  = ipportInner;
        pNatPort->_ipportNat    = HTONS((WORD)_iNatPortNext);
        pNatPort->_ipaOuter     = ipaOuter;
        pNatPort->_ipportOuter  = ipportOuter;
        pNatPort->_dwTick       = _pXnIpNatInner->TimerTick();

        InsertTailList(pleHead, &pNatPort->_leHash);
        InsertTailList(&_leLru, &pNatPort->_leLru);

        _ppNatPort[_iNatPortNext - _iNatPortBase] = pNatPort;

        _cNatPortFree -= 1;

        TraceSz5(NatPort, "** Port mapping %d [%s:%d %s:%d] allocated",
                 NTOHS(pNatPort->_ipportNat), pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner),
                 pNatPort->_ipaOuter.Str(), NTOHS(pNatPort->_ipportOuter));
    }

    return(pNatPort);
}

CXnNat::CNatPort * CXnNat::NatPortLookup(CIpPort ipportNat)
{
    UINT iNatPort = NTOHS(ipportNat);

    if (iNatPort < _iNatPortBase || iNatPort > _iNatPortLast)
        return(NULL);
    else
        return(_ppNatPort[iNatPort - _iNatPortBase]);
}

BOOL CXnNat::NatPortExpire(CNatPort * pNatPort)
{
    if (pNatPort->_dwTick + _iTimeout > _pXnIpNatInner->TimerTick())
    {
        return(FALSE);
    }
    
    TraceSz5(NatPort, "** Port mapping %d [%s:%d %s:%d] has timed out",
             NTOHS(pNatPort->_ipportNat), pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner),
             pNatPort->_ipaOuter.Str(), NTOHS(pNatPort->_ipportOuter));

    NatPortFree(pNatPort);
    return(TRUE);
}

void CXnNat::NatPortFree(CNatPort * pNatPort)
{
    UINT iNatPort = NTOHS(pNatPort->_ipportNat);

    TraceSz5(NatPort, "** Port mapping %d [%s:%d %s:%d] freed",
             iNatPort, pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner),
             pNatPort->_ipaOuter.Str(), NTOHS(pNatPort->_ipportOuter));

    Assert(iNatPort >= _iNatPortBase && iNatPort <= _iNatPortLast);
    Assert(_ppNatPort[iNatPort - _iNatPortBase] == pNatPort);
    _ppNatPort[iNatPort - _iNatPortBase] = NULL;

    RemoveEntryList(&pNatPort->_leHash);
    RemoveEntryList(&pNatPort->_leLru);

    if (pNatPort->_pNatFilt)
    {
        HalFree(pNatPort->_pNatFilt);
    }

    HalFree(pNatPort);

    _cNatPortFree += 1;
}

CXnNat::CNatFilt * CXnNat::NatFiltLookup(CNatPort * pNatPort, CIpAddr ipaOuter, CIpPort ipportOuter, BOOL fAlloc)
{
    CNatFilt *  pNatFilt;
    UINT        idx, idxIns;
    DWORD       dwTimeout = _pXnIpNatInner->TimerTick() - _iTimeout;

    if ((_iFilter & NAT_FILTER_ADDRESS) == 0)
    {
        ipaOuter = 0;
    }

    if ((_iFilter & NAT_FILTER_PORT) == 0)
    {
        ipportOuter = 0;
    }

    for (int iLo = 0, iHi = (int)pNatPort->_cNatFilt, iMid; iLo < iHi; )
    {
        iMid     = (iLo + iHi) >> 1;
        pNatFilt = &pNatPort->_pNatFilt[iMid];

        if (pNatFilt->_ipaOuter == ipaOuter && pNatFilt->_ipportOuter == ipportOuter)
        {
            if (!fAlloc && pNatFilt->_dwTick <= dwTimeout)
            {
                return(NULL);
            }

            return(pNatFilt);
        }

        if (pNatFilt->_ipaOuter < ipaOuter || (pNatFilt->_ipaOuter == ipaOuter && pNatFilt->_ipportOuter < ipportOuter))
            iLo = iMid + 1;
        else
            iHi = iMid;
    }

    idxIns = iLo;

    if (!fAlloc)
    {
        return(NULL);
    }

    if (pNatPort->_cNatFilt > 0 && pNatPort->_cNatFilt == pNatPort->_cNatFiltAlloc)
    {
        CNatFilt * pNatFiltDst = pNatPort->_pNatFilt;
        CNatFilt * pNatFiltEnd = pNatFiltDst + pNatPort->_cNatFilt;

        pNatFilt = pNatFiltDst;

        for (idx = 0; pNatFilt < pNatFiltEnd; ++pNatFilt)
        {
            if (pNatFilt->_dwTick > dwTimeout)
            {
                if (pNatFiltDst < pNatFilt)
                {
                    memcpy(pNatFiltDst, pNatFilt, sizeof(CNatFilt));

                    TraceSz7(NatPort, "** Port filter  %d [%s:%d %s:%d] <--> [%s:%d] has timed out",
                             NTOHS(pNatPort->_ipportNat), pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner),
                             pNatPort->_ipaOuter.Str(), NTOHS(pNatPort->_ipportOuter),
                             pNatFilt->_ipaOuter.Str(), NTOHS(pNatFilt->_ipportOuter));
                }

                pNatFiltDst += 1;
                idx += 1;
            }
            else
            {
                pNatPort->_cNatFilt -= 1;
                if (idxIns > idx)
                    idxIns -= 1;
            }
        }
    }

    if (pNatPort->_cNatFilt == pNatPort->_cNatFiltAlloc)
    {
        UINT cNatFiltAlloc = pNatPort->_cNatFiltAlloc + 8;

        pNatFilt = (CNatFilt *)HalAlloc(cNatFiltAlloc * sizeof(CNatFilt), PTAG_CNatFilt);

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

        if (pNatPort->_cNatFilt > 0)
        {
            memcpy(pNatFilt, pNatPort->_pNatFilt, pNatPort->_cNatFilt * sizeof(CNatFilt));
            HalFree(pNatPort->_pNatFilt);
        }

        pNatPort->_pNatFilt = pNatFilt;
        pNatPort->_cNatFiltAlloc = cNatFiltAlloc;
    }

    Assert(idxIns <= pNatPort->_cNatFilt);
    Assert(idxIns <  pNatPort->_cNatFiltAlloc);

    pNatFilt = &pNatPort->_pNatFilt[idxIns];

    if (idxIns < pNatPort->_cNatFilt)
    {
        memmove(pNatFilt + 1, pNatFilt, (pNatPort->_cNatFilt - idxIns) * sizeof(CNatFilt));
    }

    pNatFilt->_ipaOuter     = ipaOuter;
    pNatFilt->_ipportOuter  = ipportOuter;
    pNatFilt->_dwTick       = _pXnIpNatInner->TimerTick();

    pNatPort->_cNatFilt    += 1;

    TraceSz7(NatPort, "** Port filter  %d [%s:%d %s:%d] <--> [%s:%d] allocated",
             NTOHS(pNatPort->_ipportNat), pNatPort->_ipaInner.Str(), NTOHS(pNatPort->_ipportInner),
             pNatPort->_ipaOuter.Str(), NTOHS(pNatPort->_ipportOuter),
             pNatFilt->_ipaOuter.Str(), NTOHS(pNatFilt->_ipportOuter));

    return(pNatFilt);
}

BOOL CXnNat::IsGateway(CIpPort ipport, CXnIpNat * pXnIpNat)
{
    return(pXnIpNat == _pXnIpNatInner || ipport == DNS_CLIENT_PORT);
}

// ---------------------------------------------------------------------------------------
// XNetNatCreate / XNetNatDelete
// ---------------------------------------------------------------------------------------

void * WSAAPI XNetNatCreate(NATCREATE * pNatCreate)
{
    CXnNat * pXnNat = new CXnNat;

    if (pXnNat == NULL)
    {
        TraceSz(Warning, "XNetNatCreate - Out of memory allocating CXnNat");
    }
    else if (!pXnNat->Init(pNatCreate))
    {
        XNetNatDelete(pXnNat);
        pXnNat = NULL;
    }

    return(pXnNat);
}

void WSAAPI XNetNatDelete(void * pvNat)
{
    CXnNat * pXnNat = (CXnNat *)pvNat;
    pXnNat->Term();
    delete pXnNat;
}

void WSAAPI XNetNatXmit(void * pvNat, void * pvPkt, BOOL fDiscard)
{
//@@@    ((CXnNat *)pvNat)->NatXmit(pvPkt, fDiscard);
}


#endif
