// ---------------------------------------------------------------------------------------
// ipdhcp.cpp
//
// Copyright (C) Microsoft Corporation
// ---------------------------------------------------------------------------------------

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

DefineTag(dhcp, 0);

#ifdef XNET_FEATURE_DHCP

//
// DHCP message format
// NOTE: default the options field to 64 bytes
// which is the size of BOOTP vendor-specified area
//

typedef struct _DhcpMessage {
    BYTE _op;                // message type
    BYTE _htype;             // hareware address type
    BYTE _hlen;              // hardware address length
    BYTE _hops;              // relay hops
    DWORD _xid;              // transaction ID
    WORD _secs;              // seconds since address acquisition process began
    WORD _flags;             // flags
    CIpAddr _ciaddr;          // client IP address
    CIpAddr _yiaddr;          // "your" (client) IP address
    CIpAddr _siaddr;          // server IP address
    CIpAddr _giaddr;          // relay agent IP address
    BYTE _chaddr[16];        // client hardware address
    BYTE _sname[64];         // optional server hostname
    BYTE _file[128];         // boot filename
    BYTE _options[64];       // optional parameters (variable length)
} DhcpMessage;

//
// Our default DHCP packet buffer is 576 bytes
//
#define BOOTP_MESSAGE_SIZE sizeof(DhcpMessage)
#define DEFAULT_DHCP_BUFSIZE 576
#define DHCPHDRLEN offsetof(DhcpMessage, _options)

//
// Minimum and maximum DHCP hardware address length
//
#define MIN_DHCP_HWADDRLEN 1
#define MAX_DHCP_HWADDRLEN 16

//
// BOOTP message type constants
//
#define BOOTREQUEST 1
#define BOOTREPLY   2

//
// Hardware type constants
//
#define HWTYPE_10MB_ETHERNET 1
#define HWTYPE_PPP           8

//
// Constants for DHCP_MESSAGE.flags
//
#define DHCP_BROADCAST 0x8000

//
// DHCP message type constants
//
#define DHCPDISCOVER    1
#define DHCPOFFER       2
#define DHCPREQUEST     3
#define DHCPDECLINE     4
#define DHCPACK         5
#define DHCPNAK         6
#define DHCPRELEASE     7
#define DHCPINFORM      8

//
// DHCP option tag constants
//

// Fixed-length options

#define DHCPOPT_PAD                     0
#define DHCPOPT_END                     255

// Standard options

#define DHCPOPT_SUBNET_MASK             1
#define DHCPOPT_TIME_OFFSET             2
#define DHCPOPT_ROUTERS                 3
#define DHCPOPT_TIME_SERVERS            4
#define DHCPOPT_IEN116_SERVERS          5
#define DHCPOPT_DNS_SERVERS             6
#define DHCPOPT_LOG_SERVERS             7
#define DHCPOPT_COOKIE_SERVERS          8
#define DHCPOPT_LPR_SERVERS             9
#define DHCPOPT_IMPRESS_SERVERS         10
#define DHCPOPT_RLP_SERVERS             11
#define DHCPOPT_HOST_NAME               12
#define DHCPOPT_BOOT_FILESIZE           13
#define DHCPOPT_DUMP_FILE               14
#define DHCPOPT_DOMAIN_NAME             15
#define DHCPOPT_SWAP_SERVER             16
#define DHCPOPT_ROOT_PATH               17
#define DHCPOPT_EXTENSIONS_PATH         18

// IP parameters (per host)

#define DHCPOPT_ENABLE_FORWARD          19
#define DHCPOPT_NONLOCAL_SRCROUTING     20
#define DHCPOPT_POLICY_FILTER           21
#define DHCPOPT_MAX_REASSEMBLY_SIZE     22
#define DHCPOPT_DEFAULT_IP_TTL          23
#define DHCPOPT_PMTU_AGING_TIMEOUT      24
#define DHCPOPT_PMTU_PLATEAU_TABLE      25

// IP parameters (per interface)

#define DHCPOPT_INTERFACE_MTU           26
#define DHCPOPT_ALL_SUBNETS_LOCAL       27
#define DHCPOPT_BROADCAST_ADDRESS       28
#define DHCPOPT_MASK_DISCOVERY          29
#define DHCPOPT_MASK_SUPPLIER           30
#define DHCPOPT_ROUTER_DISCOVERY        31
#define DHCPOPT_SOLICIT_ROUTER_ADDR     32
#define DHCPOPT_STATIC_ROUTES           33

// Link layer parameters

#define DHCPOPT_TRAILER_ENCAPS          34
#define DHCPOPT_ARP_CACHE_TIMEOUT       35
#define DHCPOPT_ETHERNET_ENCAPS         36

// TCP parameters

#define DHCPOPT_DEFAULT_TCP_TTL         37
#define DHCPOPT_KEEPALIVE_INTERVAL      38
#define DHCPOPT_KEEPALIVE_GARBAGE       39

// Application parameters

#define DHCPOPT_NIS_DOMAIN              40
#define DHCPOPT_NIS_SERVERS             41
#define DHCPOPT_NTP_SERVERS             42

// DHCP extensions

#define DHCPOPT_REQUESTED_CIpAddr        50
#define DHCPOPT_IPADDR_LEASE_TIME       51
#define DHCPOPT_FIELD_OVERLOAD          52
#define DHCPOPT_TFTP_SERVER_NAME        66
#define DHCPOPT_BOOT_FILENAME           67
#define DHCPOPT_DHCP_MESSAGE_TYPE       53
#define DHCPOPT_SERVERID                54
#define DHCPOPT_PARAM_REQUEST_LIST      55
#define DHCPOPT_MESSAGE                 56
#define DHCPOPT_MAX_DHCP_MESSAGESIZE    57
#define DHCPOPT_T1_INTERVAL             58
#define DHCPOPT_T2_INTERVAL             59
#define DHCPOPT_VENDOR_CLASSID          60
#define DHCPOPT_CLIENTID                61

// Vendor-specific information

#define DHCPOPT_VENDOR_INFO             43
#define DHCPOPT_NETBIOS_NAMESERVERS     44
#define DHCPOPT_NETBIOS_DATASERVERS     45
#define DHCPOPT_NETBIOS_NODETYPE        46
#define DHCPOPT_NETBIOS_SCOPE           47
#define DHCPOPT_X11_FONTSERVERS         48
#define DHCPOPT_X11_DISPLAYSERVERS      49
#define DHCPOPT_NIS2_DOMAIN             64
#define DHCPOPT_NIS2_SERVERS            65
#define DHCPOPT_MOBILEIP_HOME_AGENTS    68
#define DHCPOPT_SMTP_SERVERS            69
#define DHCPOPT_POP3_SERVERS            70
#define DHCPOPT_NNTP_SERVERS            71
#define DHCPOPT_WWW_SERVERS             72
#define DHCPOPT_FINGER_SERVERS          73
#define DHCPOPT_IRC_SERVERS             74
#define DHCPOPT_STREETTALK_SERVERS      75
#define DHCPOPT_STREETTALKDA_SERVERS    76

//
// DHCP magic cookie: 99, 130, 83, 99
//
//#define DHCPCOOKIELEN 4
//extern const BYTE DhcpMagicCookie[DHCPCOOKIELEN];

//
// DHCP time value (in seconds)
//
// NOTE: We use 32-bit unsigned interface to represent
// the number of seconds ellapsed since the start of 1/1/2000.
// This should last until year 2136 before overflowing.
//

#define DHCPTIME_INFINITE 0xffffffff

//
// Autonet address range: 169.254/16
//  don't use the first 256 and the last 256 addresses
//
#define AUTONET_ADDRMASK    0xffff0000
#define AUTONET_ADDRBASE    0xa9fe0100
#define AUTONET_ADDRRANGE   0x0000fcff

//
// States for an interface
//
enum {
    STATE_NONE,             // temporary wait state after startup
    STATE_STATIC_ADDR,      // statically assigned address
    STATE_INIT,             // sending discover
    STATE_REQUESTING,       // waiting for DHCPACK after selecting
    STATE_INIT_REBOOT,      // rebooting with valid lease
    STATE_BOUND,            // bound with valid address lease
    STATE_RENEWING,         // renewing after T1 expires
    STATE_REBINDING,        // rebinding after T2 expires
    STATE_DECLINING,        // temporary wait state after sending decline
    STATE_INIT_AUTOADDR,    // waiting for timer to kick off autonet
    STATE_SELECT_AUTOADDR   // checking autonet address conflicts
};

//
// Constants for the DhcpInfo.flags field:
//
#define FLAG_ACTIVE_DHCPADDR    0x00010000
#define FLAG_ACTIVE_AUTONETADDR 0x00020000
#define FLAG_ACTIVE_NOADDR      0x00040000
#define FLAG_ACTIVE_ADDRMASK    (FLAG_ACTIVE_DHCPADDR | FLAG_ACTIVE_AUTONETADDR | FLAG_ACTIVE_NOADDR)

BOOL CXnIp::ActiveDhcpAddr()
{
    return (_flags & FLAG_ACTIVE_DHCPADDR);
}

BOOL CXnIp::ActiveAutonetAddr()
{
    return (_flags & FLAG_ACTIVE_AUTONETADDR);
}

BOOL CXnIp::ActiveStaticAddr()
{
    return(_state == STATE_STATIC_ADDR);
}

BOOL CXnIp::ActiveNoAddr()
{
    return(_flags & FLAG_ACTIVE_NOADDR);
}

//
// Dump DHCP message in the debugger
//

#if DBG
VOID DhcpDumpMessage(DhcpMessage* msg, UINT msglen);
#else
#define DhcpDumpMessage(msg, msglen)
#endif

// ---------------------------------------------------------------------------------------

//
// First 4 bytes of the options field in a DHCP message
// must match the following magic cookie
//
const BYTE DhcpMagicCookie[] = { 99, 130, 83, 99 };

//
// Default address lease time requested:
//  let the server decide
//
//UINT cfgDefaultLeaseTime = 0;

//
// Minimum retransmission timeout while in
// RENEWING and REBINDING states: 60 seconds
//
//UINT cfgMinRenewTimeout = 60;

//
// Number of seconds to wait after reboot
// before we start sending out DHCPDISCOVER packets
//
// NOTE: RFC2131 recommends that we wait between 1 to 10 seconds.
// But we're only doing 1 seconds here for faster startup time.
//
//UINT cfgStartupWaitMin = 0;
//UINT cfgStartupWaitMax = 1;

//
// Number of times we'll attempt to resend
// DHCPDISCOVER and DHCPREQUEST packets
//
// NOTE: Non-standard behavior!!!
//  We're capping the timeout between retries to a maximum of 10 seconds.
//
//UINT cfgDhcpRetryCount = 3;
//UINT cfgDhcpRetryMaxTimeout = 10;

//
// Number of autonet addresses we'll attempt before giving up
//
//UINT cfgAutonetAddrRetries = 10;

//
// How frequently to look for DHCP server when in Autonet mode
//
//UINT cfgAutonetDhcpCheckInterval = 5*60;

//
// How many ARP requests to send
// when checking for address conflict
//
//UINT cfgConflictCheckRetries = 2;

// Check to see if we're forced to use autonet address
//  (without trying to find DHCP servers)
INLINE
BOOL CXnIp::DhcpForceAutonet(CIpAddr addr, CIpAddr mask)
{
    ICHECK(IP, USER|UDPC|SDPC);

    return (mask == HTONL(AUTONET_ADDRMASK)) && 
            (addr & mask) == (HTONL(AUTONET_ADDRBASE) & mask);
}

NTSTATUS CXnIp::DhcpInit()
{
    ICHECK(IP, USER);

    _state = STATE_NONE;
    _timerDhcp.Init((PFNTIMER)DhcpTimer);
    _dwXidNext = RandLong();

    return(NETERR_OK);
}

INT CXnIp::DhcpConfig(const XNetConfigParams * pxncp)
{
    ICHECK(IP, UDPC);

    //@@@ Cannot be reconfigured.  Need DHCP rewrite.

    _activeaddr = pxncp->ina.s_addr;

    if (_activeaddr != 0)
    {
        _activemask = pxncp->inaMask.s_addr;

        if (_activemask == 0 || !_activemask.IsValidMask())
            _activemask = _activeaddr.DefaultMask();

        _options._gatewayCount = 0;

        if (pxncp->inaGateway.s_addr != 0)
        {
            _options._gateways[_options._gatewayCount] = pxncp->inaGateway.s_addr;
            _options._gatewayCount += 1;
        }

        _options._dnsServerCount = 0;

        if (pxncp->inaDnsPrimary.s_addr != 0)
        {
            _options._dnsServers[_options._dnsServerCount] = pxncp->inaDnsPrimary.s_addr;
            _options._dnsServerCount += 1;
        }

        if (pxncp->inaDnsSecondary.s_addr != 0)
        {
            _options._dnsServers[_options._dnsServerCount] = pxncp->inaDnsSecondary.s_addr;
            _options._dnsServerCount += 1;
        }
    }

    if (!TestInitFlag(INITF_CONNECTED_BOOT))
    {
        TraceSz(Warning, "Ethernet cable not plugged in.  No IP address acquired.");
        _flags = (_flags & ~FLAG_ACTIVE_ADDRMASK) | FLAG_ACTIVE_NOADDR;
    }
#if defined(XNET_FEATURE_INSECURE) && !defined(XNET_FEATURE_SG)
    else if (!(cfgFlags & XNET_STARTUP_BYPASS_SECURITY))
    {
        // When operating in secure mode don't acquire an IP address so we behave
        // more like xnets.lib.

        _flags = (_flags & ~FLAG_ACTIVE_ADDRMASK) | FLAG_ACTIVE_NOADDR;
    }
#endif
    else if (_activeaddr != 0 && !DhcpForceAutonet(_activeaddr, _activemask))
    {
        // If we're using static address, inform the IP stack
        IpSetAddress(_activeaddr, _activemask);
        DhcpSetDefaultGateways();
        DhcpChangeState(STATE_STATIC_ADDR);
    }
    else if ((cfgFlags & XNET_STARTUP_BYPASS_DHCP) || _activeaddr != 0)
    {
        TraceSz(Warning, "Forcing autonet...");
        _activeaddr = _activemask = 0;
        DhcpChangeState(STATE_INIT_AUTOADDR);
    }
    else
    {
        // Obtain address via DHCP or Autonet.  Be quiet for a while after startup.
        DhcpTimerSetRelative(0, 0);
    }

    return(0);
}

void CXnIp::DhcpTerm()
{
    ICHECK(IP, UDPC);
    TimerSet(&_timerDhcp, TIMER_INFINITE);
}

void CXnIp::DhcpRecv(CPacket * ppkt, CUdpHdr * pUdpHdr, UINT msglen)
{
    ICHECK(IP, UDPC|SDPC);

    DhcpMessage * msg = (DhcpMessage *)(pUdpHdr + 1);
    CDhcpOptions * dhcpParams = NULL;
    BYTE* option;
    BYTE overload = 0;
    NTSTATUS status = NETERR_NOTIMPL;

    if (msglen < DHCPHDRLEN)
    {
        TraceSz1(pktRecv, "[DISCARD] DHCP packet is too short (%d)", msglen);
        return;
    }

    if (memcmp(msg->_chaddr, _ea._ab, sizeof(CEnetAddr)) != 0)
    {
        TraceSz(pktRecv, "[DISCARD] DHCP packet not meant for me");
        return;
    }

    if (    msg->_op != BOOTREPLY
        ||  msg->_htype != HWTYPE_10MB_ETHERNET
        ||  msg->_hlen != sizeof(CEnetAddr)
        ||  NTOHL(msg->_xid) != _xid)
    {
        TraceSz4(pktWarn, "[DISCARD] DHCP packet is invalid (%d,%d,%d,%d)",
                 msg->_op != BOOTREPLY,
                 msg->_htype != HWTYPE_10MB_ETHERNET,
                 msg->_hlen != sizeof(CEnetAddr),
                 NTOHL(msg->_xid) != _xid);
        return;
    }

    DhcpDumpMessage(msg, msglen);

    dhcpParams = (CDhcpOptions *)PoolAllocZ(sizeof(*dhcpParams), PTAG_CDhcpOptions);

    if (!dhcpParams)
    {
        TraceSz(pktWarn, "[DISCARD] Out of memory allocating CDhcpOptions");
        status = NETERR_MEMORY;
        goto exit;
    }

    //
    // Skip the DHCP magic cookie
    //
    option = msg->_options;
    msglen -= DHCPHDRLEN;
    if (    msglen < sizeof(DhcpMagicCookie)
        ||  memcmp(option, DhcpMagicCookie, sizeof(DhcpMagicCookie)) != 0)
    {
        TraceSz1(pktWarn, "[DISCARD] DHCP packet has invalid magic cookie (%d)", msglen);
        goto exit;
    }

    msglen -= sizeof(DhcpMagicCookie);
    option += sizeof(DhcpMagicCookie);

    //
    // Parse the regular options
    //
    status = DhcpParseOptionParams(dhcpParams, option, msglen, &overload);

    if (!NT_SUCCESS(status))
    {
        TraceSz(pktWarn, "[DISCARD] DHCP regular options are invalid");
        goto exit;
    }

    //
    // Parse overloaded options in the msg->_file field
    //
    if (overload & 1)
    {
        status = DhcpParseOptionParams(dhcpParams, msg->_file, sizeof(msg->_file), NULL);

        if (!NT_SUCCESS(status))
        {
            TraceSz(pktWarn, "[DISCARD] DHCP file options are invalid");
            goto exit;
        }
    }

    //
    // Parse overloaded options in the msg->_sname field
    //
    if (overload & 2)
    {
        status = DhcpParseOptionParams(dhcpParams, msg->_sname, sizeof(msg->_sname), NULL); 

        if (!NT_SUCCESS(status))
        {
            TraceSz(pktWarn, "[DISCARD] DHCP sname options are invalid");
            goto exit;
        }
    }

    //
    // The received packet must have a server identifier option
    //
    if (dhcpParams->_dhcpServer == 0)
    {
        TraceSz(pktWarn, "[DISCARD] DHCP packet lacks server identifier option");
        status = NETERR_PARAM;
        goto exit;
    }

    //
    // Now that we've parsed the option data,
    // use it appropriately
    //
    status = NETERR_NOTIMPL;

    switch (_state) {
    case STATE_INIT:
        // Expecting DHCPOFFER...
        if (dhcpParams->_recvMsgType == DHCPOFFER)
            status = DhcpProcessOffer(msg->_yiaddr, dhcpParams);
        break;

    case STATE_INIT_REBOOT:
    case STATE_REQUESTING:
    case STATE_RENEWING:
    case STATE_REBINDING:
        // Expecting DHCPACK or DHCPNAK...
        if (dhcpParams->_recvMsgType == DHCPACK)
            status = DhcpProcessAck(msg->_yiaddr, dhcpParams);
        else if (dhcpParams->_recvMsgType == DHCPNAK)
            status = DhcpProcessNak(dhcpParams->_dhcpServer);
        break;
    }

exit:

    if (!NT_SUCCESS(status) && status != NETERR_NOTIMPL)
    {
        TraceSz1(pktWarn, "[DISCARD] DhcpRecv failed: %08lX", status);
    }

    PoolFree(dhcpParams);
}

void CXnIp::DhcpNotifyAddressConflict()
{
    ICHECK(IP, UDPC|SDPC);

    if (_state == STATE_SELECT_AUTOADDR)
    {
        // the last autonet address we chose was no good
        // so we need to find another one
        DhcpSelectAutonetAddr();
    }
    else
    {
        TraceSz1(Warning, "!!! Address conflict: %s", _activeaddr.Str());
    }
}

//
// Number of seconds from start of 1601 to start of 2000
//
static const LONGLONG StartOf2000 = 0x2ee7dd480;

CXnIp::DHCPTIME CXnIp::DhcpTime()
{
    ICHECK(IP, UDPC|SDPC);

    LARGE_INTEGER currentTime;

    // Get the current UTC time
    //  = number of 100 nanoseconds since 1/1/1601
    KeQuerySystemTime(&currentTime);

    // Return the number of seconds since the start of 2000
    return (DHCPTIME) (currentTime.QuadPart / 10000000 - StartOf2000);
}


void CXnIp::DhcpTimerSetRelative(UINT minWait, UINT maxWait)
{
    ICHECK(IP, UDPC|SDPC);

    TimerSet(&_timerDhcp, TimerTick() + (minWait + RandScaled(maxWait - minWait)) * TICKS_PER_SECOND);
}

void CXnIp::DhcpTimerSetAbsolute(DHCPTIME dhcptime)
{
    ICHECK(IP, UDPC|SDPC);

    DWORD dwTick = TIMER_INFINITE;

    if (dhcptime != DHCPTIME_INFINITE)
    {
        DHCPTIME dhcptimeNow = DhcpTime();

        if (dhcptime > dhcptimeNow)
            dwTick = TimerTick() + (dhcptime - dhcptimeNow) * TICKS_PER_SECOND;
        else
            dwTick = TimerTick();
    }

    TimerSet(&_timerDhcp, dwTick);
}

void CXnIp::DhcpComputeTimeout()
{
    ICHECK(IP, UDPC|SDPC);

    UINT minWait, maxWait;

    switch (_state)
    {
    case STATE_INIT:
        if (ActiveAutonetAddr()) {
            //
            // Special case: we're currently using an Autonet address
            // and we're sending out periodic DHCPDISCOVER packets.
            //
            // DhcpSetIfTimerRelative(
            //     cfgAutonetDhcpCheckInterval,
            //     cfgAutonetDhcpCheckInterval);

            // NOTE: Non-standard behavior!!!
            //  We do not send out DHCPDISCOVER messages
            //  while we're using an active AutoNet address.
            DhcpTimerSetAbsolute(DHCPTIME_INFINITE);
            break;
        }

        // Fall through

    case STATE_REQUESTING:
    case STATE_INIT_REBOOT:
        //
        // timeout = 2**retry x 2 +/- 1, i.e.
        //  4 +/- 1
        //  8 +/- 1
        //  16 +/- 1
        //  32 +/- 1
        //

        minWait = 2 << _retries;
        if (minWait > cfgDhcpRetryMaxTimeoutInSeconds)
            minWait = cfgDhcpRetryMaxTimeoutInSeconds;

        minWait -= 1;
        maxWait = minWait + 2;
        DhcpTimerSetRelative(minWait, maxWait);
        break;

    case STATE_BOUND:
        //
        // timeout when T1 expires
        //
        DhcpTimerSetAbsolute(_options._t1time);
        break;

    case STATE_RENEWING:
    case STATE_REBINDING: {
        //
        // calculate retransmission timeout for
        // RENEWING and REBINDING states:
        //  1. half the time from now to T2 (renew) or expiration (rebind)
        //  2. make sure it's at least 60 seconds
        //

        DHCPTIME t1, t2;
        t1 = DhcpTime();
        t2 = (_state == STATE_RENEWING) ? _options._t2time : _options._exptime;

        if (t1 < t2) {
            minWait = (t2-t1) / 2;
            if (minWait >= cfgDhcpRenewMinTimeoutInSeconds)
                t1 += minWait;
            else {
                t1 += cfgDhcpRenewMinTimeoutInSeconds;
                if (t1 > t2) t1 = t2;
            }
        }

        DhcpTimerSetAbsolute(t1);
        }
        break;

    case STATE_DECLINING:
        //
        // Wait 10 seconds after sending DHCP decline
        //
        DhcpTimerSetRelative(cfgDhcpDeclineWaitInSeconds, cfgDhcpDeclineWaitInSeconds);
        break;
    
    case STATE_SELECT_AUTOADDR:
        //
        // Wait ~2 seconds for address conflict detection
        //
        DhcpTimerSetRelative(cfgAutoIpRexmitTimeoutInSeconds, cfgAutoIpRexmitTimeoutInSeconds);
        break;

    default:
        // Should not happen - shut off the timer just in case
        Assert(FALSE);

    case STATE_NONE:
    case STATE_STATIC_ADDR:
        DhcpTimerSetAbsolute(DHCPTIME_INFINITE);
        break;
    }
}

void CXnIp::DhcpTimer(CTimer * pt)
{
    ICHECK(IP, UDPC|SDPC);

    switch (_state) {
    case STATE_NONE:
        //
        // We got here because we just finished the quiet
        // period after startup
        //
        // If we had a valid lease before, start in
        // INIT-REBOOT state; otherwise, start in INIT state
        //
        DhcpChangeState(_dhcpaddr ? STATE_INIT_REBOOT : STATE_INIT);
        break;
    
    case STATE_INIT:
        //
        // We're sending DHCPDISCOVER messages
        //
        if (_retries >= cfgDhcpRetryCount && !ActiveAutonetAddr())
        {
            //
            // Too many retries, give up and
            // start Autonet address acquisition process
            //
            TraceSz(dhcp, "Couldn't discover DHCP server, trying autonet...");
            DhcpChangeState(STATE_SELECT_AUTOADDR);
        }
        else
        {
            //
            // Send out another DHCPDISCOVER packet
            //
            TraceSz(dhcp, "Retransmit DHCPDISCOVER");
            DhcpSendDiscover();
        }
        break;

    case STATE_INIT_REBOOT:
    case STATE_REQUESTING:
        //
        // We're sending DHCPREQUEST
        //
        if (_retries >= cfgDhcpRetryCount) {
            //
            // Too many retries, go back to INIT state
            //
            TraceSz(dhcp, _state == STATE_INIT_REBOOT ?
                        "Failed to reuse a prior lease" :
                        "No ack for an offered address");

            DhcpChangeState(STATE_INIT);
        } else {
            //
            // Send out another DHCPREQUEST packet
            //
            TraceSz(dhcp, "Retransmit DHCPREQUEST");
            DhcpSendRequest();
        }
        break;
    
    case STATE_DECLINING:
        //
        // We just finished waiting after sending DHCPDECLINE
        //
        DhcpChangeState(STATE_INIT);
        break;
    
    case STATE_BOUND:
        //
        // T1 expired, start the renewing process
        //
        TraceSz(dhcp, "Switching to RENEWING state");
        DhcpChangeState(STATE_RENEWING);
        break;

    case STATE_RENEWING:
        //
        // We're trying to renew a valid address.
        // If T2 expired, start the rebinding process.
        //
        if (DhcpTime() >= _options._t2time) {
            TraceSz(dhcp, "Switching to REBINDING state");
            DhcpChangeState(STATE_REBINDING);
        } else {
            // Send out another DHCPREQUEST
            TraceSz(dhcp, "Retransmit DHCPREQUEST");
            DhcpSendRequest();
        }
        break;

    case STATE_REBINDING:
        //
        // We're trying to rebind a valid lease
        //  did our lease expire?
        //
        if (DhcpTime() >= _options._exptime) {
            // Inform IP stack to discard the active address
            TraceSz(dhcp, "Address lease expired - start over");
            DhcpResetInterface();

            // Too bad, go back to INIT state
            DhcpChangeState(STATE_INIT);
        } else {
            // Send out another DHCPREQUEST
            TraceSz(dhcp, "Retransmit DHCPREQUEST");
            DhcpSendRequest();
        }
        break;

    case STATE_INIT_AUTOADDR:
        DhcpChangeState(STATE_SELECT_AUTOADDR);
        break;

    case STATE_SELECT_AUTOADDR:
        if (++_retries > cfgAutoIpRetriesPerAttempt)
        {
            // We've successfully picked an autonet address.
            TraceSz1(dhcp, "Selected autonet address: %s", _autonetaddr.Str());

            _activeaddr = _autonetaddr;
            _activemask = HTONL(AUTONET_ADDRMASK);
            _flags |= FLAG_ACTIVE_AUTONETADDR;
            IpSetAddress(_activeaddr, _activemask);

            // Switch to INIT state to continue looking
            // for a DHCP server
            _initRetryCount = 0;
            DhcpChangeState(STATE_INIT);
        }
        else
        {
            // No response to our previous ARP request.
            // Try again just to be sure.
            EnetXmitArp(_autonetaddr);
            DhcpComputeTimeout();
        }
        break;

    default:
        TraceSz(dhcp, "Unexpected timeout");
        DhcpTimerSetAbsolute(DHCPTIME_INFINITE);
        break;
    }
}


void CXnIp::DhcpChangeState(INT state)
{
    ICHECK(IP, UDPC|SDPC);

    INT oldstate = _state;

    _state = state;
    _retries = 0;

    // Assign a new transaction ID for the next outgoing message.

    _xid = _dwXidNext++;

    switch (state) {
    case STATE_INIT_REBOOT:
    case STATE_REQUESTING:
    case STATE_RENEWING:
    case STATE_REBINDING:
        // Send out DHCPREQUEST
        DhcpSendRequest();
        break;

    case STATE_INIT:
        if (oldstate == STATE_SELECT_AUTOADDR) {
            // We just selected an autonet address.
            // Continue to look for a DHCP server.
            DhcpComputeTimeout();
        }
        else if (++_initRetryCount > cfgDhcpRetryCount) {
            // We went through the INIT state too many times
            // without getting a valid address lease. Just give up.
            // We don't try Autonet because in this case there
            // is a DHCP server but somehow we can't work with it.
            TraceSz(Warning, "Couldn't get a valid DHCP address after many tries");
            DhcpChangeState(STATE_NONE);
            _flags = (_flags & ~FLAG_ACTIVE_ADDRMASK) | FLAG_ACTIVE_NOADDR;
        } else {
            // Send out DHCPDISCOVER
            DhcpSendDiscover();
        }
        break;

    case STATE_BOUND:
        DhcpComputeTimeout();
        TraceSz1(dhcp, "Sleep %d seconds till renewal...", _options._t1time - DhcpTime());
        break;

    case STATE_INIT_AUTOADDR:
        DhcpTimerSetAbsolute(0);
        break;

    case STATE_SELECT_AUTOADDR:
        _initRetryCount = 0;
        DhcpSelectAutonetAddr();
        break;

    case STATE_NONE:
    case STATE_STATIC_ADDR:
        DhcpTimerSetAbsolute(DHCPTIME_INFINITE);
        break;
    }
}

void CXnIp::DhcpResetInterface()
{
    ICHECK(IP, UDPC|SDPC);

    IpSetAddress(0, 0);
    _flags &= ~FLAG_ACTIVE_ADDRMASK;
    if (_state != STATE_STATIC_ADDR)
        _activeaddr = 0;
}


NTSTATUS CXnIp::DhcpSendMessage(CPacket * ppkt, UINT msglen, BOOL broadcast)
{
    ICHECK(IP, UDPC|SDPC);

    ppkt->SetCb(ppkt->GetCb() - DEFAULT_DHCP_BUFSIZE + msglen);

    CUdpHdr * pUdpHdr = ppkt->GetUdpHdr();
    DhcpMessage * msg = (DhcpMessage *)(pUdpHdr + 1);

    // Debug trace
    DhcpDumpMessage(msg, msglen);

    // Set broadcast flag if necessary
    if (!_activeaddr || ActiveAutonetAddr())
        msg->_flags |= HTONS(DHCP_BROADCAST);

    pUdpHdr->_ipportSrc = DHCP_CLIENT_PORT;
    pUdpHdr->_ipportDst = DHCP_SERVER_PORT;
    pUdpHdr->_wLen = HTONS(msglen + sizeof(CUdpHdr));

    // Call the IP layer to send out the packet
    IpFillAndXmit(ppkt, broadcast ? IPADDR_BROADCAST : _options._dhcpServer, IPPROTOCOL_UDP);

    // Calculate the timeout value
    _retries++;
    DhcpComputeTimeout();

    return(NETERR_OK);
}

//
// Append the 'parameter request list' option
//  !!! we assume the data buffer is large enough.
//
BYTE * DhcpAppendParamReqList(BYTE * option)
{
    *option++ = DHCPOPT_PARAM_REQUEST_LIST;
    *option++ = 4;
    *option++ = DHCPOPT_SUBNET_MASK;
    *option++ = DHCPOPT_ROUTERS;
    *option++ = DHCPOPT_DNS_SERVERS;
    *option++ = DHCPOPT_DOMAIN_NAME;
    return(option);
}

//
// Append an option whose value is a DWORD
//
BYTE * DhcpAppendDWordOption(BYTE * option, INT tag, DWORD val)
{
    option[0] = (BYTE) tag;
    option[1] = sizeof(DWORD);
    option += 2;

    memcpy(option, &val, sizeof(DWORD));
    return option + sizeof(DWORD);
}


//
// Append the 'address lease time' option
//
BYTE * CXnIp::DhcpAppendLeaseTimeOption(BYTE * option)
{
    ICHECK(IP, UDPC|SDPC);

    DWORD dwLeaseTime = cfgDhcpDefaultLeaseTimeInDays * (24 * 60 * 60);

    if (dwLeaseTime == 0)
        return option;

    return DhcpAppendDWordOption(option, DHCPOPT_IPADDR_LEASE_TIME, HTONL(dwLeaseTime));
}


BYTE * CXnIp::DhcpFillMessageHeader(BYTE * buf, INT msgtype)
{
    ICHECK(IP, UDPC|SDPC);

    DhcpMessage* msg = (DhcpMessage*) buf;
    BYTE* option = msg->_options;
    BYTE hwtype;

    memset(buf, 0, DEFAULT_DHCP_BUFSIZE);
    msg->_op = BOOTREQUEST;
    msg->_htype = hwtype = HWTYPE_10MB_ETHERNET;
    msg->_hlen = sizeof(CEnetAddr);
    memcpy(msg->_chaddr, _ea._ab, sizeof(CEnetAddr));

    //
    // Fill in the transaction ID field
    // NOTE: We reuse the same XID for retransmissions.
    //
    msg->_xid = HTONL(_xid);

    // Number of seconds since we started the address
    // acquisition process.
    msg->_secs = (WORD) HTONS((WORD)_secsSinceStart);

    // Start with the magic cookie
    memcpy(option, DhcpMagicCookie, sizeof(DhcpMagicCookie));
    option += sizeof(DhcpMagicCookie);

    // Append the message type option
    option[0] = DHCPOPT_DHCP_MESSAGE_TYPE;
    option[1] = 1;
    option[2] = (BYTE) msgtype;
    option += 3;

    // Append the client identifier option
    option[0] = DHCPOPT_CLIENTID;
    option[1] = (BYTE) (sizeof(CEnetAddr)+1);
    option[2] = hwtype;
    option += 3;
    memcpy(option, _ea._ab, sizeof(CEnetAddr));
    option += sizeof(CEnetAddr);

    return option;
}


void CXnIp::DhcpComputeSecsSinceStart()
{
    ICHECK(IP, UDPC|SDPC);

    DHCPTIME now = DhcpTime();

    if (_retries == 0)
        _acqtime = now;

    _secsSinceStart = now - _acqtime;
}

//
// Common prolog and epilog for SendDhcpXXX functions
//
#define SEND_DHCP_MESSAGE_PROLOG() \
        DhcpMessage* msg; \
        BYTE* buf; \
        BYTE* option; \
        CPacket * pkt = PacketAlloc(PTAG_CDhcpPacket, PKTF_TYPE_UDP|PKTF_XMIT_INSECURE|PKTF_POOLALLOC, DEFAULT_DHCP_BUFSIZE); \
        if (!pkt) return NETERR_MEMORY; \
        buf = (BYTE *)pkt->GetUdpHdr() + sizeof(CUdpHdr); \
        msg = (DhcpMessage*) buf

#define SEND_DHCP_MESSAGE_RETURN(_bcast) \
        return DhcpSendMessage(pkt, option - buf, _bcast)


NTSTATUS CXnIp::DhcpSendDiscover()
{
    ICHECK(IP, UDPC|SDPC);

    SEND_DHCP_MESSAGE_PROLOG();

    Assert(_state == STATE_INIT);

    DhcpComputeSecsSinceStart();

    // Fill in common header fields
    option = DhcpFillMessageHeader(buf, DHCPDISCOVER);

    // Fill in the parameter request list
    option = DhcpAppendParamReqList(option);

    // Fill in requested ip address and lease time option
    if (_dhcpaddr)
        option = DhcpAppendDWordOption(option, DHCPOPT_REQUESTED_CIpAddr, _dhcpaddr);

    // Fill in the lease time option
    option = DhcpAppendLeaseTimeOption(option);
    *option++ = DHCPOPT_END;

    // Emit the message
    SEND_DHCP_MESSAGE_RETURN(TRUE);
}


NTSTATUS CXnIp::DhcpSendRequest()
{
    ICHECK(IP, UDPC|SDPC);

    INT state = _state;
    SEND_DHCP_MESSAGE_PROLOG();

    Assert(state == STATE_REQUESTING ||
           state == STATE_RENEWING ||
           state == STATE_REBINDING ||
           state == STATE_INIT_REBOOT);

    //
    // If we're in REQUESTING state, then don't update
    // the secsSinceStart field. This is so that the
    // secs in DHCPREQUEST message will be the same as
    // what's in the original DHCPDISCOVER message.
    //
    if (state != STATE_REQUESTING) {
        DhcpComputeSecsSinceStart();
    }

    // Fill in common header fields
    option = DhcpFillMessageHeader(buf, DHCPREQUEST);

    // Fill in the ciaddr field and the 'requested ip addr' option
    if (state == STATE_RENEWING || state == STATE_REBINDING) {
        msg->_ciaddr = _dhcpaddr;
    } else if (_dhcpaddr) {
        // state == STATE_INIT_REBOOT || state == STATE_REQUESTING
        option = DhcpAppendDWordOption(option, DHCPOPT_REQUESTED_CIpAddr, _dhcpaddr);
    }

    // Fill in the parameter request list
    option = DhcpAppendParamReqList(option);

    // Fill in the lease time option
    option = DhcpAppendLeaseTimeOption(option);

    // Fill in the server identifier option
    if (state == STATE_REQUESTING)
        option = DhcpAppendDWordOption(option, DHCPOPT_SERVERID, _options._dhcpServer);

    *option++ = DHCPOPT_END;

    // Emit the message:
    //  unicast in RENEWING state, broadcast otherwise
    SEND_DHCP_MESSAGE_RETURN(state != STATE_RENEWING);
}

BOOL CXnIp::DhcpValidateOffer(CIpAddr yiaddr, CDhcpOptions* param)
{
    ICHECK(IP, UDPC|SDPC);

    if (    !yiaddr.IsValidUnicast()
        ||  !param->_dhcpServer.IsValidUnicast()
        ||  param->_exptime < (DHCPTIME)(8*cfgDhcpRenewMinTimeoutInSeconds))
        return FALSE;

    if (param->_dhcpmask == 0)
        param->_dhcpmask = yiaddr.DefaultMask();

    if (param->_t1time == 0 ||
        param->_t2time == 0 ||
        param->_t1time >= param->_t2time ||
        param->_t2time - param->_t1time < cfgDhcpRenewMinTimeoutInSeconds ||
        param->_t2time >= param->_exptime ||
        param->_exptime - param->_t2time < cfgDhcpRenewMinTimeoutInSeconds) {
        param->_t1time = param->_exptime / 2;
        param->_t2time = param->_exptime * 7 / 8;
    }

    return TRUE;
}


NTSTATUS CXnIp::DhcpProcessOffer(CIpAddr yiaddr, CDhcpOptions* param)
{
    ICHECK(IP, UDPC|SDPC);

    Assert(_state == STATE_INIT);

    TraceSz3(dhcp, "Received DHCPOFFER %s from %s @ time %d", yiaddr.Str(), param->_dhcpServer.Str(), DhcpTime());

    // Simply sanity check of offered parameters
    if (!DhcpValidateOffer(yiaddr, param))
        return(NETERR_PARAM);

    _options._dhcpServer = param->_dhcpServer;
    _dhcpaddr = yiaddr;

    // Send DHCPREQUEST and ignore error
    DhcpChangeState(STATE_REQUESTING);

    return(NETERR_OK);
}


void CXnIp::DhcpAddOrRemoveGateways(BOOL fDelete)
{
    ICHECK(IP, UDPC|SDPC);

    CIpAddr ipa;
    UINT    i;

    for (i = 0; i < _options._gatewayCount; i++)
    {
        ipa = _options._gateways[i];

        TraceSz2(dhcp, "%s gateway: %s", fDelete ? "Remove" : "Add", ipa.Str());

        if (fDelete)
            RouteDelete(0, 0, ipa);
        else
            RouteAdd(0, 0, ipa, RTEF_DEFAULT, (WORD)(RTE_DEFAULT_METRIC + i));
    }
}

void CXnIp::DhcpSetDefaultGateways()
{
    ICHECK(IP, USER|UDPC|SDPC);

    RaiseToDpc();
    DhcpAddOrRemoveGateways(FALSE);
}


void CXnIp::DhcpUseOptionParams(CDhcpOptions* param)
{
    ICHECK(IP, UDPC|SDPC);

    BOOL resetGateways;

    //
    // Remember the originating server address and lease info
    //
    _options._dhcpServer = param->_dhcpServer;

    if (param->_exptime == DHCPTIME_INFINITE) {
        _options._t1time =
        _options._t2time =
        _options._exptime = DHCPTIME_INFINITE;
    } else {
        _options._t1time = _acqtime + param->_t1time;
        _options._t2time = _acqtime + param->_t2time;
        _options._exptime = _acqtime + param->_exptime;
    }

    //
    // Set gateways in the IP stack
    //
    if (param->_gatewayCount == 0 || _options._gatewayCount == 0) {
        resetGateways = TRUE;
    } else {
        UINT oldcnt = _options._gatewayCount;
        UINT newcnt = param->_gatewayCount;
        UINT i, j;

        for (i=0; i < newcnt; i++) {
            for (j=0; j < oldcnt; j++)
                if (param->_gateways[i] != _options._gateways[j]) break;
            if (j < oldcnt) break;
        }
        resetGateways = (i < newcnt);
    }

    if (resetGateways) {
        //
        // Reset gateways if anything has changed
        //  first delete existing gateways
        //  then set new gateways
        //
        // NOTE: should we ping the new gateways here?
        //
        DhcpAddOrRemoveGateways(TRUE);

        _options._gatewayCount = param->_gatewayCount;
        memcpy(_options._gateways,
                param->_gateways,
                param->_gatewayCount * sizeof(CIpAddr));

        DhcpAddOrRemoveGateways(FALSE);
    }

    _options._dnsServerCount = param->_dnsServerCount;
    memcpy(_options._dnsServers,
           param->_dnsServers,
           param->_dnsServerCount * sizeof(CIpAddr));

#if DBG
    UINT i;

    TraceSz2(dhcp, "%d gateway%s configured", _options._gatewayCount, _options._gatewayCount == 1 ? "" : "s");
    for (i = 0; i < _options._gatewayCount; ++i)
        TraceSz1(dhcp, "  %s", _options._gateways[i].Str());
    TraceSz2(dhcp, "%d DNS server%s configured", _options._dnsServerCount, _options._dnsServerCount == 1 ? "" : "s");
    for (i = 0; i < _options._dnsServerCount; ++i)
        TraceSz1(dhcp, "  %s", _options._dnsServers[i].Str());
#endif

}

NTSTATUS CXnIp::DhcpProcessAck(CIpAddr yiaddr, CDhcpOptions* param)
{
    ICHECK(IP, UDPC|SDPC);

    TraceSz2(dhcp, "Received DHCPACK %s from %s", yiaddr.Str(), param->_dhcpServer.Str());

    Assert(_state == STATE_INIT_REBOOT ||
           _state == STATE_REQUESTING ||
           _state == STATE_RENEWING ||
           _state == STATE_REBINDING);

    // Simply sanity check of offered parameters
    if (!DhcpValidateOffer(yiaddr, param))
        return NETERR_PARAM;

    // Note: We're not checking for address conflicts
    // and just assume the offered address is valid.

    // If we're currently using a different address, give it up
    if ((_activeaddr != 0) &&
        (_activeaddr != yiaddr ||
         _activemask != param->_dhcpmask) ||
         ActiveAutonetAddr())
    {
        TraceSz1(dhcp, "Giving up old IP address %s", _activeaddr.Str());
        DhcpResetInterface();
    }

    // If we got a new address, set it down in the IP stack
    if (_activeaddr == 0)
    {
        IpSetAddress(yiaddr, param->_dhcpmask);
        _activeaddr = _dhcpaddr = yiaddr;
        _activemask = _options._dhcpmask = param->_dhcpmask;
        _flags |= FLAG_ACTIVE_DHCPADDR;
    }

    TraceSz2(dhcp, "Accepted IP address: %s from %s", yiaddr.Str(), param->_dhcpServer.Str());
    TraceSz3(dhcp, "Lease time: %d / %d / %d", param->_t1time, param->_t2time, param->_exptime);

    //
    // Set other option parameters
    //
    _initRetryCount = 0;
    DhcpUseOptionParams(param);

    // We're now in bound state.
    // Set timer to expire at T1 time.
    DhcpChangeState(STATE_BOUND);
    return NETERR_OK;
}

NTSTATUS CXnIp::DhcpProcessNak(CIpAddr dhcpServer)
{
    ICHECK(IP, UDPC|SDPC);

    TraceSz1(dhcp, "Received DHCPNAK from %s", dhcpServer.Str());

    Assert(_state == STATE_INIT_REBOOT ||
           _state == STATE_REQUESTING ||
           _state == STATE_RENEWING ||
           _state == STATE_REBINDING);

    // Barf if we got DHCPNAK from an unexpected server
    if (_state != STATE_INIT_REBOOT && dhcpServer != _options._dhcpServer)
    {
        TraceSz1(Warning, "Random DHCPNAK from %s?", dhcpServer.Str());
    }

    // If we're using an address, give it up
    if (_activeaddr) {
        DhcpResetInterface();
    }

    //
    // Go to INIT state to start over again
    // Send DHCPDISCOVER and ignore error
    //
    DhcpChangeState(STATE_INIT);
    return NETERR_OK;
}


// NOTE: we're assuming little-endian machine here.
#define EXTRACT_DWORD_OPTION(_result) \
        if (len != 4) goto exit; \
        if (_result == 0) { \
            _result = (((DWORD) buf[0] << 24) | \
                       ((DWORD) buf[1] << 16) | \
                       ((DWORD) buf[2] <<  8) | \
                       ((DWORD) buf[3]      )); \
        }

#define EXTRACT_IPADDR_OPTION(_result) \
        if (len != 4) goto exit; \
        if (_result == 0) _result = *((CIpAddr*) buf)

NTSTATUS CXnIp::DhcpParseOptionParams(CDhcpOptions* param, const BYTE* buf, UINT buflen, BYTE* overload)
{
    ICHECK(IP, UDPC|SDPC);

    while (buflen) {
        UINT tag, len;

        // Stop after seeing the 'end' option
        if ((tag = buf[0]) == DHCPOPT_END) break;

        // Skip the 'pad' option
        if (tag == DHCPOPT_PAD) {
            buf++; buflen--;
            continue;
        }

        // Is the option length field valid?
        if (buflen < 2 || buflen-2 < (len = buf[1])) goto exit;
        buf += 2; buflen -= 2;
    
        // Interpret option data
        switch (tag) {
        case DHCPOPT_DHCP_MESSAGE_TYPE:
            if (len != 1) goto exit;
            if (param->_recvMsgType == 0)
                param->_recvMsgType = *buf;
            break;

        case DHCPOPT_SERVERID:
            EXTRACT_IPADDR_OPTION(param->_dhcpServer);
            break;

        case DHCPOPT_SUBNET_MASK:
            EXTRACT_IPADDR_OPTION(param->_dhcpmask);
            break;

        case DHCPOPT_ROUTERS:
        case DHCPOPT_DNS_SERVERS:
        {
            UINT* pcount;
            CIpAddr* parray;
            UINT n;

            if (len == 0 || len % sizeof(CIpAddr) != 0) goto exit;
            if (tag == DHCPOPT_ROUTERS)
            {
                pcount = &param->_gatewayCount;
                parray = param->_gateways;
                n = MAX_DEFAULT_GATEWAYS * sizeof(CIpAddr);
            }
            else
            {
                pcount = &param->_dnsServerCount;
                parray = param->_dnsServers;
                n = MAX_DEFAULT_DNSSERVERS * sizeof(CIpAddr);
            }
        
            if (n > len) n = len;
            if (*pcount == 0) {
                *pcount = n / sizeof(CIpAddr);
                memcpy(parray, buf, n);
            }
            break;
        }

        case DHCPOPT_IPADDR_LEASE_TIME:
            EXTRACT_DWORD_OPTION(param->_exptime);
            break;

        case DHCPOPT_T1_INTERVAL:
            EXTRACT_DWORD_OPTION(param->_t1time);
            break;

        case DHCPOPT_T2_INTERVAL:
            EXTRACT_DWORD_OPTION(param->_t2time);
            break;

        case DHCPOPT_FIELD_OVERLOAD:
            if (len != 1) goto exit;
            if (overload && *overload == 0) *overload = *buf;
            break;
        }

        buf += len; buflen -= len;
    }

    return NETERR_OK;

exit:
    TraceSz(Warning, "Invalid option data");
    return NETERR_PARAM;
}

void CXnIp::DhcpSelectAutonetAddr()
{
    ICHECK(IP, UDPC|SDPC);

    CIpAddr addr;

    Assert(_state == STATE_SELECT_AUTOADDR);

    if (++_initRetryCount > cfgAutoIpMaxAttempts) {
        //
        // We tried too many autonet addresses without success.
        // Just give up.
        //
        _initRetryCount = 0;
        DhcpChangeState(STATE_NONE);

        // Signal that Xnet initialization was completed abnormally
        _flags = (_flags & ~FLAG_ACTIVE_ADDRMASK) | FLAG_ACTIVE_NOADDR;
        TraceSz(Warning, "Failed to pick an autonet address.");
        return;
    }

    // Generate a random autonet address
    addr = AUTONET_ADDRBASE + RandScaled(AUTONET_ADDRRANGE);
    _autonetaddr = addr = HTONL(addr);

    TraceSz1(dhcp, "Trying autonet address: %s", addr.Str());

    EnetXmitArp(addr);
    _retries = 0;
    DhcpComputeTimeout();
}

#if DBG

DefineTag(dhcpDump, 0);

//
// Whether to dump incoming and outgoing DHCP messages
//
VOID DhcpDumpByteArray(const BYTE* buf, UINT buflen)

/*++

Routine Description:

    Dump a byte array in hexdecimal format

Arguments:

    buf - Points to the data buffer
    buflen - Buffer length

Return Value:

    NONE

--*/

{
    for ( ; buflen--; buf++) {
        TraceSz1(dhcpDump, "+%02x+", *buf);
    }
}


VOID DhcpDumpOption(IN const BYTE* buf, IN UINT buflen, OUT BYTE* overload)

/*++

Routine Description:

    Dump DHCP options

Arguments:

    buf - Points to the option data buffer
    buflen - Data buffer length
    overload - Returns the option overload flags

Return Value:

    NONE

--*/

{
    static PCSTR dhcpMessageTypeStrs[] = {
        "***",
        "DHCPDISCOVER",
        "DHCPOFFER",
        "DHCPREQUEST",
        "DHCPDECLINE",
        "DHCPACK",
        "DHCPNAK",
        "DHCPRELEASE",
        "DHCPINFORM"
    };

    UINT tag, len, val;

    while (buflen && *buf != DHCPOPT_END) {
        // Special case for DHCPOPT_PAD - single byte
        if (*buf == DHCPOPT_PAD) {
            TraceSz(dhcpDump, "+    PAD");
            buflen--, buf++;
            continue;
        }

        // Check option length
        if (buflen < 2 || buflen-2 < (len = buf[1])) goto badopt;

        switch (tag = buf[0]) {
        case DHCPOPT_DHCP_MESSAGE_TYPE:
            if (len != 1) goto badopt;
            val = buf[2];
            if (val > 0 && val < dimensionof(dhcpMessageTypeStrs)) {
                TraceSz1(dhcpDump, "+    %s+", dhcpMessageTypeStrs[val]);
            } else {
                TraceSz1(dhcpDump, "+    Unknown DHCP message type: %d+", val);
            }
            break;

        case DHCPOPT_FIELD_OVERLOAD:
            if (len != 1) goto badopt;
            TraceSz1(dhcpDump, "+    OVERLOAD: %d+", buf[2]);
            if (overload)
                *overload = buf[2];
            else
                TraceSz(dhcpDump, "+ !!!+");
            break;

        case DHCPOPT_REQUESTED_CIpAddr:
        case DHCPOPT_SERVERID:
            if (len != 4) goto badopt;
            TraceSz5(dhcpDump, "+    %s %d.%d.%d.%d+",
                     (tag == DHCPOPT_SERVERID) ? "SERVERID" : "REQUEST IP ADDR",
                     buf[2], buf[3], buf[4], buf[5]);
            break;

        default:
            TraceSz1(dhcpDump, "+    %d - +", tag);
            DhcpDumpByteArray(buf+2, len);
            break;
        }

        TraceSz(dhcpDump, "+ ");
        buf += len+2;
        buflen -= len+2;
    }

    if (buflen == 0) {
        TraceSz(dhcpDump, "!!! Missing 'end' option");
    } else {
        do {
            buflen--, buf++;
        } while (buflen && *buf == 0);

        if (buflen != 0)
        {
            TraceSz(dhcpDump, "!!! Extra data after 'end' option");
        }
    }
    return;

badopt:
    TraceSz(dhcpDump, "!!! Bad DHCP option data");
}


void DhcpDumpMessage(DhcpMessage* msg, UINT msglen)

/*++

Routine Description:

    Dump the content of a DHCP message

Arguments:

    msg - Points to the DHCP message
    msglen - Message length

Return Value:

    NONE

--*/

{
    const BYTE* option;
    BYTE overload = 0;

    if (!Tag(dhcpDump))
        return;

    //
    // Sanity check
    //
    if (msglen < DHCPHDRLEN) {
        TraceSz1(dhcpDump, "!!! DHCP message too small: %d bytes", msglen);
        return;
    }

    //
    // Dump out fixed header information
    //
    TraceSz(dhcpDump, "DHCP message: ");
    if (msg->_op == BOOTREQUEST)
        TraceSz(dhcpDump, "BOOTREQUEST");
    else if (msg->_op == BOOTREPLY)
        TraceSz(dhcpDump, "BOOTREPLY");
    else
        TraceSz1(dhcpDump, "%d", msg->_op);

    TraceSz1(dhcpDump, "  htype: %d", msg->_htype);
    if (msg->_hlen > sizeof(msg->_chaddr)) {
        TraceSz1(dhcpDump, "!!! Invalid hardware address length: %d\n", msg->_hlen);
    } else if (msg->_hlen) {
        TraceSz(dhcpDump, "  chaddr: +");
        DhcpDumpByteArray(msg->_chaddr, msg->_hlen);
        TraceSz(dhcpDump, "+ ");
    }

    TraceSz1(dhcpDump, "  hops: %d", msg->_hops);
    TraceSz1(dhcpDump, "  xid: 0x%08x", NTOHL(msg->_xid));
    TraceSz1(dhcpDump, "  secs: %d", NTOHS(msg->_secs));
    TraceSz1(dhcpDump, "  flags: 0x%04x", NTOHS(msg->_flags));
    TraceSz1(dhcpDump, "  ciaddr: %s", msg->_ciaddr.Str());
    TraceSz1(dhcpDump, "  yiaddr: %s", msg->_yiaddr.Str());
    TraceSz1(dhcpDump, "  siaddr: %s", msg->_siaddr.Str());
    TraceSz1(dhcpDump, "  giaddr: %s", msg->_giaddr.Str());

    //
    // Dump options
    //
    option = msg->_options;
    msglen -= DHCPHDRLEN;

    if (msglen < sizeof(DhcpMagicCookie) ||
        memcmp(option, DhcpMagicCookie, sizeof(DhcpMagicCookie)) != 0) {
        TraceSz(dhcpDump, "!!! Invalid DHCP magic cookie");
        return;
    }
    msglen -= sizeof(DhcpMagicCookie);
    option += sizeof(DhcpMagicCookie);

    TraceSz(dhcpDump, "  options:");
    DhcpDumpOption(option, msglen, &overload);

    if (overload & 1) {
        TraceSz(dhcpDump, "  overload options (file):");
        DhcpDumpOption(msg->_file, sizeof(msg->_file), NULL);
    }

    if (overload & 2) {
        TraceSz(dhcpDump, "  overload options (sname):");
        DhcpDumpOption(msg->_sname, sizeof(msg->_sname), NULL);
    }
}

#endif // DBG

void CXnIp::IpRecvArp(CEnetAddr * pea)
{
    ICHECK(IP, UDPC|SDPC);
    DhcpNotifyAddressConflict();
}

#endif // XNET_FEATURE_DHCP
