root/src/servers/net/DHCPClient.cpp
/*
 * Copyright 2006-2018, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler, axeld@pinc-software.de
 *              Vegard Wærp, vegarwa@online.no
 *              Philippe Houdoin, <phoudoin at haiku-os dot org>
 */


#include "DHCPClient.h"

#include <Message.h>
#include <MessageRunner.h>
#include <NetworkDevice.h>
#include <NetworkInterface.h>

#include <algorithm>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <unistd.h>

#include <Debug.h>
#include <Message.h>
#include <MessageRunner.h>
#include <Looper.h>

#include "NetServer.h"


// See RFC 2131 for DHCP, see RFC 1533 for BOOTP/DHCP options

#define DHCP_CLIENT_PORT        68
#define DHCP_SERVER_PORT        67

#define DEFAULT_TIMEOUT         0.25    // secs
#define MAX_TIMEOUT                     64              // secs

#define AS_USECS(t) (1000000 * t)

#define MAX_RETRIES                     5

enum message_opcode {
        BOOT_REQUEST = 1,
        BOOT_REPLY
};

enum message_option {
        OPTION_MAGIC = 0x63825363,

        // generic options
        OPTION_PAD = 0,
        OPTION_END = 255,
        OPTION_SUBNET_MASK = 1,
        OPTION_TIME_OFFSET = 2,
        OPTION_ROUTER_ADDRESS = 3,
        OPTION_DOMAIN_NAME_SERVER = 6,
        OPTION_HOST_NAME = 12,
        OPTION_DOMAIN_NAME = 15,
        OPTION_MAX_DATAGRAM_SIZE = 22,
        OPTION_INTERFACE_MTU = 26,
        OPTION_BROADCAST_ADDRESS = 28,
        OPTION_NETWORK_TIME_PROTOCOL_SERVERS = 42,
        OPTION_NETBIOS_NAME_SERVER = 44,
        OPTION_NETBIOS_SCOPE = 47,

        // DHCP specific options
        OPTION_REQUEST_IP_ADDRESS = 50,
        OPTION_ADDRESS_LEASE_TIME = 51,
        OPTION_OVERLOAD = 52,
        OPTION_MESSAGE_TYPE = 53,
        OPTION_SERVER_ADDRESS = 54,
        OPTION_REQUEST_PARAMETERS = 55,
        OPTION_ERROR_MESSAGE = 56,
        OPTION_MAX_MESSAGE_SIZE = 57,
        OPTION_RENEWAL_TIME = 58,
        OPTION_REBINDING_TIME = 59,
        OPTION_CLASS_IDENTIFIER = 60,
        OPTION_CLIENT_IDENTIFIER = 61,
};

enum message_type {
        DHCP_NONE = 0,
        DHCP_DISCOVER = 1,
        DHCP_OFFER = 2,
        DHCP_REQUEST = 3,
        DHCP_DECLINE = 4,
        DHCP_ACK = 5,
        DHCP_NACK = 6,
        DHCP_RELEASE = 7,
        DHCP_INFORM = 8
};

struct dhcp_option_cookie {
        dhcp_option_cookie()
                :
                state(0),
                file_has_options(false),
                server_name_has_options(false)
        {
        }

        const uint8* next;
        uint8   state;
        bool    file_has_options;
        bool    server_name_has_options;
};

struct dhcp_message {
        dhcp_message(message_type type);

        uint8           opcode;
        uint8           hardware_type;
        uint8           hardware_address_length;
        uint8           hop_count;
        uint32          transaction_id;
        uint16          seconds_since_start;
        uint16          flags;
        in_addr_t       client_address;
        in_addr_t       your_address;
        in_addr_t       server_address;
        in_addr_t       gateway_address;
        uint8           mac_address[16];
        uint8           server_name[64];
        uint8           file[128];
        uint32          options_magic;
        uint8           options[1260];

        size_t MinSize() const { return 576; }
        size_t Size() const;

        bool HasOptions() const;
        bool NextOption(dhcp_option_cookie& cookie, message_option& option,
                const uint8*& data, size_t& size) const;
        const uint8* FindOption(message_option which) const;
        const uint8* LastOption() const;
        message_type Type() const;

        uint8* PrepareMessage(uint8 type);
        uint8* PutOption(uint8* options, message_option option);
        uint8* PutOption(uint8* options, message_option option, uint8 data);
        uint8* PutOption(uint8* options, message_option option, uint16 data);
        uint8* PutOption(uint8* options, message_option option, uint32 data);
        uint8* PutOption(uint8* options, message_option option, const uint8* data,
                uint32 size);
        uint8* FinishOptions(uint8* options);

        static const char* TypeToString(message_type type);
} _PACKED;

struct socket_timeout {
        socket_timeout(int socket)
                :
                timeout(time_t(AS_USECS(DEFAULT_TIMEOUT))),
                tries(0)
        {
                UpdateSocket(socket);
        }

        bigtime_t timeout; // in micro secs
        uint8 tries;

        bool Shift(int socket, bigtime_t stateMaxTime, const char* device);
        void UpdateSocket(int socket) const;
};

#define DHCP_FLAG_BROADCAST             0x8000

#define ARP_HARDWARE_TYPE_ETHER 1

const uint32 kMsgLeaseTime = 'lstm';

static const uint8 kRequestParameters[] = {
        OPTION_SUBNET_MASK, OPTION_ROUTER_ADDRESS,
        OPTION_DOMAIN_NAME_SERVER, OPTION_BROADCAST_ADDRESS,
        OPTION_DOMAIN_NAME
};


dhcp_message::dhcp_message(message_type type)
{
        // ASSERT(this == offsetof(this, opcode));
        memset(this, 0, sizeof(*this));
        options_magic = htonl(OPTION_MAGIC);

        uint8* next = PrepareMessage(type);
        FinishOptions(next);
}


bool
dhcp_message::HasOptions() const
{
        return options_magic == htonl(OPTION_MAGIC);
}


bool
dhcp_message::NextOption(dhcp_option_cookie& cookie,
        message_option& option, const uint8*& data, size_t& size) const
{
        if (!HasOptions())
                return false;

        if (cookie.state == 0) {
                cookie.state++;
                cookie.next = options;
        }

        uint32 bytesLeft = 0;

        switch (cookie.state) {
                case 1:
                        // options from "options"
                        bytesLeft = sizeof(options) - (cookie.next - options);
                        break;

                case 2:
                        // options from "file"
                        bytesLeft = sizeof(file) - (cookie.next - file);
                        break;

                case 3:
                        // options from "server_name"
                        bytesLeft = sizeof(server_name) - (cookie.next - server_name);
                        break;
        }

        while (true) {
                if (bytesLeft == 0) {
                        cookie.state++;

                        // handle option overload in file and/or server_name fields.
                        switch (cookie.state) {
                                case 2:
                                        // options from "file" field
                                        if (cookie.file_has_options) {
                                                bytesLeft = sizeof(file);
                                                cookie.next = file;
                                        }
                                        break;

                                case 3:
                                        // options from "server_name" field
                                        if (cookie.server_name_has_options) {
                                                bytesLeft = sizeof(server_name);
                                                cookie.next = server_name;
                                        }
                                        break;

                                default:
                                        // no more place to look for options
                                        // if last option is not OPTION_END,
                                        // there is no space left for other option!
                                        if (option != OPTION_END)
                                                cookie.next = NULL;
                                        return false;
                        }

                        if (bytesLeft == 0) {
                                // no options in this state, try next one
                                continue;
                        }
                }

                option = (message_option)cookie.next[0];
                if (option == OPTION_END) {
                        bytesLeft = 0;
                        continue;
                } else if (option == OPTION_PAD) {
                        bytesLeft--;
                        cookie.next++;
                        continue;
                }

                size = cookie.next[1];
                data = &cookie.next[2];
                cookie.next += 2 + size;
                bytesLeft -= 2 + size;

                if (option == OPTION_OVERLOAD) {
                        cookie.file_has_options = data[0] & 1;
                        cookie.server_name_has_options = data[0] & 2;
                        continue;
                }

                return true;
        }
}


const uint8*
dhcp_message::FindOption(message_option which) const
{
        dhcp_option_cookie cookie;
        message_option option;
        const uint8* data;
        size_t size;
        while (NextOption(cookie, option, data, size)) {
                // iterate through all options
                if (option == which)
                        return data;
        }

        return NULL;
}


const uint8*
dhcp_message::LastOption() const
{
        dhcp_option_cookie cookie;
        message_option option;
        const uint8* data;
        size_t size;
        while (NextOption(cookie, option, data, size)) {
                // iterate through all options
        }

        return cookie.next;
}


message_type
dhcp_message::Type() const
{
        const uint8* data = FindOption(OPTION_MESSAGE_TYPE);
        if (data)
                return (message_type)data[0];

        return DHCP_NONE;
}


size_t
dhcp_message::Size() const
{
        const uint8* last = LastOption();

        if (last < options) {
                // if last option is stored above "options" field, it means
                // the whole options field and message is already filled...
                return sizeof(dhcp_message);
        }

        return sizeof(dhcp_message) - sizeof(options) + last + 1 - options;
}


uint8*
dhcp_message::PrepareMessage(uint8 type)
{
        uint8* next = options;
        next = PutOption(next, OPTION_MESSAGE_TYPE, type);
        next = PutOption(next, OPTION_MAX_MESSAGE_SIZE,
                (uint16)htons(sizeof(dhcp_message)));
        return next;
}


uint8*
dhcp_message::PutOption(uint8* options, message_option option)
{
        options[0] = option;
        return options + 1;
}


uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint8 data)
{
        return PutOption(options, option, &data, 1);
}


uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint16 data)
{
        return PutOption(options, option, (uint8*)&data, sizeof(data));
}


uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint32 data)
{
        return PutOption(options, option, (uint8*)&data, sizeof(data));
}


uint8*
dhcp_message::PutOption(uint8* options, message_option option,
        const uint8* data, uint32 size)
{
        // TODO: check enough space is available

        options[0] = option;
        options[1] = size;
        memcpy(&options[2], data, size);

        return options + 2 + size;
}


uint8*
dhcp_message::FinishOptions(uint8* options)
{
        return PutOption(options, OPTION_END);
}


/*static*/ const char*
dhcp_message::TypeToString(message_type type)
{
        switch (type) {
#define CASE(x) case x: return #x;
                CASE(DHCP_NONE)
                CASE(DHCP_DISCOVER)
                CASE(DHCP_OFFER)
                CASE(DHCP_REQUEST)
                CASE(DHCP_DECLINE)
                CASE(DHCP_ACK)
                CASE(DHCP_NACK)
                CASE(DHCP_RELEASE)
                CASE(DHCP_INFORM)
#undef CASE
        }

        return "<unknown>";
}


void
socket_timeout::UpdateSocket(int socket) const
{
        struct timeval value;
        value.tv_sec = timeout / 1000000;
        value.tv_usec = timeout % 1000000;
        setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value));
}


bool
socket_timeout::Shift(int socket, bigtime_t stateMaxTime, const char* device)
{
        if (tries == UINT8_MAX)
                return false;

        tries++;

        if (tries > MAX_RETRIES) {
                bigtime_t now = system_time();
                if (stateMaxTime == -1 || stateMaxTime < now)
                        return false;
                bigtime_t remaining = (stateMaxTime - now) / 2 + 1;
                timeout = std::max(remaining, bigtime_t(AS_USECS(60)));
        } else
                timeout += timeout;

        if (timeout > AS_USECS(MAX_TIMEOUT))
                timeout = AS_USECS(MAX_TIMEOUT);

        syslog(LOG_DEBUG, "%s: Timeout shift: %" B_PRIdTIME " msecs (try %" B_PRIu8 ")\n",
                device, timeout / 1000, tries);

        UpdateSocket(socket);
        return true;
}


//      #pragma mark -


DHCPClient::DHCPClient(BMessenger target, const char* device)
        :
        AutoconfigClient("dhcp", target, device),
        fConfiguration(kMsgConfigureInterface),
        fResolverConfiguration(kMsgConfigureResolver),
        fRunner(NULL),
        fNegotiateThread(-1),
        fAssignedAddress(0),
        fServer(AF_INET, NULL, DHCP_SERVER_PORT, B_UNCONFIGURED_ADDRESS_FAMILIES),
        fStartTime(0),
        fRequestTime(0),
        fRenewalTime(0),
        fRebindingTime(0),
        fLeaseTime(0)
{
        fTransactionID = (uint32)system_time() ^ rand();

        BNetworkAddress link;
        BNetworkInterface interface(device);
        fStatus = interface.GetHardwareAddress(link);
        if (fStatus != B_OK)
                return;

        memcpy(fMAC, link.LinkLevelAddress(), sizeof(fMAC));

        if ((interface.Flags() & IFF_AUTO_CONFIGURED) != 0) {
                // Check for interface previous auto-configured address, if any.
                BNetworkInterfaceAddress interfaceAddress;
                int index = interface.FindFirstAddress(AF_INET);
                if (index >= 0
                        && interface.GetAddressAt(index, interfaceAddress) == B_OK) {
                        BNetworkAddress address = interfaceAddress.Address();
                        const sockaddr_in& addr = (sockaddr_in&)address.SockAddr();
                        fAssignedAddress = addr.sin_addr.s_addr;

                        if ((ntohl(fAssignedAddress) & IN_CLASSB_NET) == 0xa9fe0000) {
                                // previous auto-configured address is a link-local one:
                                // there is no point asking a DHCP server to renew such
                                // server-less assigned address...
                                fAssignedAddress = 0;
                        }
                }
        }
}


DHCPClient::~DHCPClient()
{
        thread_id thread = fNegotiateThread;
        if (thread != -1) {
                fNegotiateThread = -1;
                suspend_thread(thread);
                resume_thread(thread);

                UnlockLooper();
                wait_for_thread(thread, NULL);
                LockLooper();
        }

        delete fRunner;

        if (fStatus != B_OK)
                return;

        int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (socket < 0)
                return;

        // release lease

        dhcp_message release(DHCP_RELEASE);
        _PrepareMessage(release, BOUND);

        _SendMessage(socket, release, fServer);
        close(socket);
}


status_t
DHCPClient::Start()
{
        if (fNegotiateThread != -1)
                return EINPROGRESS;

        fNegotiateThread = spawn_thread(_NegotiatorThread, "DHCP negotiator",
                B_NORMAL_PRIORITY, this);
        return resume_thread(fNegotiateThread);
}


status_t
DHCPClient::_NegotiatorThread(void* data)
{
        openlog_thread("DHCP", 0, LOG_DAEMON);

        DHCPClient* client = (DHCPClient*)data;
        client->LockLooper();
        client->fStatus = client->_Negotiate();
        syslog(LOG_DEBUG, "%s: DHCP status = %s\n", client->Device(), strerror(client->fStatus));
        client->fNegotiateThread = -1;
        client->UnlockLooper();

        closelog_thread();
        return B_OK;
}


status_t
DHCPClient::_Negotiate()
{
        dhcp_state state = _CurrentState();
        if (state == BOUND)
                return B_OK;

        fStartTime = system_time();
        fTransactionID++;

        char hostName[MAXHOSTNAMELEN];
        if (gethostname(hostName, MAXHOSTNAMELEN) == 0)
                fHostName.SetTo(hostName, MAXHOSTNAMELEN);
        else
                fHostName.Truncate(0);

        int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (socket < 0)
                return errno;

        // Enable reusing the port. This is needed in case there is more
        // than 1 interface that needs to be configured. Note that the only reason
        // this works is because there is code below to bind to a specific
        // interface.
        int option = 1;
        setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &option, sizeof(option));

        BNetworkAddress local;
        local.SetToWildcard(AF_INET, DHCP_CLIENT_PORT);

        option = 1;
        setsockopt(socket, SOL_SOCKET, SO_BROADCAST, &option, sizeof(option));

        if (bind(socket, local, local.Length()) < 0) {
                close(socket);
                return errno;
        }

        bigtime_t previousLeaseTime = fLeaseTime;

        status_t status = B_OK;
        while (state != BOUND && fNegotiateThread != -1) {
                status = _StateTransition(socket, state);
                if (status != B_OK && (state == SELECTING || state == REBOOTING))
                        break;
        }

        close(socket);

        if (fLeaseTime == 0)
                fLeaseTime = previousLeaseTime;
        if (fLeaseTime == 0)
                fLeaseTime = 60;

        if (fRenewalTime == 0)
                fRenewalTime = fLeaseTime / 2;
        if (fRebindingTime == 0)
                fRebindingTime = fLeaseTime * 7 / 8;
        fLeaseTime += fRequestTime;
        fRenewalTime += fRequestTime;
        fRebindingTime += fRequestTime;
        _RestartLease(fRenewalTime);

        fStatus = status;
        if (status != B_OK) {
                // If we were asked to quit, don't inform the looper that we failed.
                if (fNegotiateThread != -1)
                        Looper()->PostMessage(kMsgAutoConfigureFailed, Looper(), this);
                return status;
        }

        // configure interface
        BMessage reply;
        status = Target().SendMessage(&fConfiguration, &reply);
        if (status == B_OK)
                status = reply.FindInt32("status", &fStatus);

        // configure resolver
        reply.MakeEmpty();
        fResolverConfiguration.AddString("device", Device());
        status = Target().SendMessage(&fResolverConfiguration, &reply);
        if (status == B_OK)
                status = reply.FindInt32("status", &fStatus);
        return status;
}


status_t
DHCPClient::_GotMessage(dhcp_state& state, dhcp_message* message)
{
        switch (state) {
                case SELECTING:
                        if (message->Type() == DHCP_OFFER) {
                                state = REQUESTING;

                                fAssignedAddress = message->your_address;
                                syslog(LOG_INFO, "  your_address: %s\n",
                                                _AddressToString(fAssignedAddress).String());

                                fConfiguration.MakeEmpty();
                                fConfiguration.AddString("device", Device());
                                fConfiguration.AddBool("auto_configured", true);

                                BMessage address;
                                address.AddString("family", "inet");
                                address.AddString("address", _AddressToString(fAssignedAddress));
                                fResolverConfiguration.MakeEmpty();
                                _ParseOptions(*message, address, fResolverConfiguration);

                                fConfiguration.AddMessage("address", &address);
                                return B_OK;
                        }

                        return B_BAD_VALUE;

                case REBOOTING:
                case REBINDING:
                case RENEWING:
                case REQUESTING:
                        if (message->Type() == DHCP_ACK) {
                                // TODO: we might want to configure the stuff, don't we?
                                BMessage address;
                                fResolverConfiguration.MakeEmpty();
                                _ParseOptions(*message, address, fResolverConfiguration);
                                        // TODO: currently, only lease time and DNS is updated this
                                        // way

                                // our address request has been acknowledged
                                state = BOUND;

                                return B_OK;
                        }

                        if (message->Type() == DHCP_NACK) {
                                // server reject our request on previous assigned address
                                // back to square one...
                                fAssignedAddress = 0;
                                state = INIT;
                                return B_OK;
                        }

                default:
                        return B_BAD_VALUE;
        }
}


status_t
DHCPClient::_StateTransition(int socket, dhcp_state& state)
{
        if (state == INIT) {
                // The local interface does not have an address yet, bind the socket
                // to the device directly.
                BNetworkDevice device(Device());
                int index = device.Index();

                setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, &index, sizeof(int));
        }

        BNetworkAddress broadcast;
        broadcast.SetToBroadcast(AF_INET, DHCP_SERVER_PORT);

        socket_timeout timeout(socket);

        dhcp_message discover(DHCP_DISCOVER);
        _PrepareMessage(discover, state);

        dhcp_message request(DHCP_REQUEST);
        _PrepareMessage(request, state);

        bool skipRequest = false;
        dhcp_state originalState = state;
        fRequestTime = system_time();
        while (true) {
                if (!skipRequest) {
                        _SendMessage(socket, originalState == INIT ? discover : request,
                                originalState == RENEWING ? fServer : broadcast);

                        if (originalState == INIT)
                                state = SELECTING;
                        else if (originalState == INIT_REBOOT)
                                state = REBOOTING;
                }

                char buffer[2048];
                struct sockaddr_in from;
                socklen_t fromLength = sizeof(from);

                UnlockLooper();
                ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer),
                        0, (struct sockaddr*)&from, &fromLength);
                LockLooper();

                if (bytesReceived < 0 && errno == B_TIMED_OUT) {
                        // depending on the state, we'll just try again
                        if (!_TimeoutShift(socket, state, timeout))
                                return B_TIMED_OUT;
                        skipRequest = false;
                        continue;
                } else if (bytesReceived < 0)
                        return errno;

                skipRequest = true;
                dhcp_message* message = (dhcp_message*)buffer;
                if (message->transaction_id != htonl(fTransactionID)
                        || !message->HasOptions()
                        || memcmp(message->mac_address, discover.mac_address,
                                discover.hardware_address_length)) {
                        // this message is not for us
                        syslog(LOG_DEBUG, "%s: Ignoring %s not for us from %s\n",
                                Device(), dhcp_message::TypeToString(message->Type()),
                                _AddressToString(from.sin_addr.s_addr).String());
                        continue;
                }

                syslog(LOG_DEBUG, "%s: Received %s from %s\n",
                        Device(), dhcp_message::TypeToString(message->Type()),
                        _AddressToString(from.sin_addr.s_addr).String());

                if (_GotMessage(state, message) == B_OK)
                        break;
        }

        return B_OK;
}


void
DHCPClient::_RestartLease(bigtime_t leaseTime)
{
        if (leaseTime == 0)
                return;

        BMessage lease(kMsgLeaseTime);
        fRunner = new BMessageRunner(this, &lease, leaseTime - system_time(), 1);
}


void
DHCPClient::_ParseOptions(dhcp_message& message, BMessage& address,
        BMessage& resolverConfiguration)
{
        dhcp_option_cookie cookie;
        message_option option;
        const uint8* data;
        size_t size;
        while (message.NextOption(cookie, option, data, size)) {
                // iterate through all options
                switch (option) {
                        case OPTION_ROUTER_ADDRESS:
                                syslog(LOG_DEBUG, "  gateway: %s\n",
                                        _AddressToString(data).String());
                                address.AddString("gateway", _AddressToString(data));
                                break;
                        case OPTION_SUBNET_MASK:
                                syslog(LOG_DEBUG, "  subnet: %s\n",
                                        _AddressToString(data).String());
                                address.AddString("mask", _AddressToString(data));
                                break;
                        case OPTION_BROADCAST_ADDRESS:
                                syslog(LOG_DEBUG, "  broadcast: %s\n",
                                        _AddressToString(data).String());
                                address.AddString("broadcast", _AddressToString(data));
                                break;
                        case OPTION_DOMAIN_NAME_SERVER:
                        {
                                for (uint32 i = 0; i < size / 4; i++) {
                                        syslog(LOG_DEBUG, "  nameserver[%d]: %s\n", i,
                                                _AddressToString(&data[i * 4]).String());
                                        resolverConfiguration.AddString("nameserver",
                                                _AddressToString(&data[i * 4]).String());
                                }
                                resolverConfiguration.AddInt32("nameserver_count",
                                        size / 4);
                                break;
                        }
                        case OPTION_SERVER_ADDRESS:
                        {
                                syslog(LOG_DEBUG, "  server: %s\n",
                                        _AddressToString(data).String());
                                status_t status = fServer.SetAddress(*(in_addr_t*)data);
                                if (status != B_OK) {
                                        syslog(LOG_ERR, "   BNetworkAddress::SetAddress failed with %s!\n",
                                                strerror(status));
                                        fServer.Unset();
                                }
                                break;
                        }

                        case OPTION_ADDRESS_LEASE_TIME:
                                syslog(LOG_DEBUG, "  lease time: %lu seconds\n",
                                        ntohl(*(uint32*)data));
                                fLeaseTime = ntohl(*(uint32*)data) * 1000000LL;
                                break;
                        case OPTION_RENEWAL_TIME:
                                syslog(LOG_DEBUG, "  renewal time: %lu seconds\n",
                                        ntohl(*(uint32*)data));
                                fRenewalTime = ntohl(*(uint32*)data) * 1000000LL;
                                break;
                        case OPTION_REBINDING_TIME:
                                syslog(LOG_DEBUG, "  rebinding time: %lu seconds\n",
                                        ntohl(*(uint32*)data));
                                fRebindingTime = ntohl(*(uint32*)data) * 1000000LL;
                                break;

                        case OPTION_HOST_NAME:
                                syslog(LOG_DEBUG, "  host name: \"%.*s\"\n", (int)size,
                                        (const char*)data);
                                break;

                        case OPTION_DOMAIN_NAME:
                        {
                                char domain[256];
                                strlcpy(domain, (const char*)data,
                                        min_c(size + 1, sizeof(domain)));

                                syslog(LOG_DEBUG, "  domain name: \"%s\"\n", domain);

                                resolverConfiguration.AddString("domain", domain);
                                break;
                        }

                        case OPTION_MESSAGE_TYPE:
                                break;

                        case OPTION_ERROR_MESSAGE:
                                syslog(LOG_INFO, "  error message: \"%.*s\"\n", (int)size,
                                        (const char*)data);
                                break;

                        default:
                                syslog(LOG_DEBUG, "  UNKNOWN OPTION %" B_PRIu32 " (0x%" B_PRIx32 ")\n",
                                        (uint32)option, (uint32)option);
                                break;
                }
        }
}


void
DHCPClient::_PrepareMessage(dhcp_message& message, dhcp_state state)
{
        message.opcode = BOOT_REQUEST;
        message.hardware_type = ARP_HARDWARE_TYPE_ETHER;
        message.hardware_address_length = 6;
        message.transaction_id = htonl(fTransactionID);
        message.seconds_since_start = htons(min_c((system_time() - fStartTime)
                / 1000000LL, 65535));
        memcpy(message.mac_address, fMAC, 6);

        message_type type = message.Type();

        uint8 *next = message.PrepareMessage(type);

        switch (type) {
                case DHCP_DISCOVER:
                        next = message.PutOption(next, OPTION_REQUEST_PARAMETERS,
                                kRequestParameters, sizeof(kRequestParameters));

                        if (fHostName.Length() > 0) {
                                next = message.PutOption(next, OPTION_HOST_NAME,
                                        reinterpret_cast<const uint8*>(fHostName.String()),
                                        fHostName.Length());
                        }
                        break;

                case DHCP_REQUEST:
                        next = message.PutOption(next, OPTION_REQUEST_PARAMETERS,
                                kRequestParameters, sizeof(kRequestParameters));

                        if (fHostName.Length() > 0) {
                                next = message.PutOption(next, OPTION_HOST_NAME,
                                        reinterpret_cast<const uint8*>(fHostName.String()),
                                        fHostName.Length());
                        }

                        if (state == REQUESTING) {
                                const sockaddr_in& server = (sockaddr_in&)fServer.SockAddr();
                                next = message.PutOption(next, OPTION_SERVER_ADDRESS,
                                        (uint32)server.sin_addr.s_addr);
                        }

                        if (state == INIT || state == INIT_REBOOT
                                || state == REQUESTING) {
                                next = message.PutOption(next, OPTION_REQUEST_IP_ADDRESS,
                                        (uint32)fAssignedAddress);
                        } else
                                message.client_address = fAssignedAddress;
                        break;

                case DHCP_RELEASE: {
                        const sockaddr_in& server = (sockaddr_in&)fServer.SockAddr();
                        next = message.PutOption(next, OPTION_SERVER_ADDRESS,
                                (uint32)server.sin_addr.s_addr);

                        message.client_address = fAssignedAddress;
                        break;
                }

                default:
                        break;
        }

        message.FinishOptions(next);
}


bool
DHCPClient::_TimeoutShift(int socket, dhcp_state& state,
        socket_timeout& timeout)
{
        bigtime_t stateMaxTime = -1;

        // Compute the date at which we must consider the DHCP negociation failed.
        // This varies depending on the current state. In renewing and rebinding
        // states, it is based on the lease expiration.
        // We can stay for up to 1 minute in the selecting and requesting states
        // (these correspond to sending DHCP_DISCOVER and DHCP_REQUEST,
        // respectively).
        // All other states time out immediately after a single try.
        // If this timeout expires, the DHCP negociation is aborted and starts
        // over. As long as the timeout is not expired, we repeat the message with
        // increasing delays (the delay is computed in timeout.shift below, and is
        // at most equal to the timeout, but usually much shorter).
        if (state == RENEWING)
                stateMaxTime = fRebindingTime;
        else if (state == REBINDING)
                stateMaxTime = fLeaseTime;
        else if (state == SELECTING || state == REQUESTING)
                stateMaxTime = fRequestTime + AS_USECS(MAX_TIMEOUT);

        if (system_time() > stateMaxTime) {
                state = state == REBINDING ? INIT : REBINDING;
                return false;
        }

        return timeout.Shift(socket, stateMaxTime, Device());
}


/*static*/ BString
DHCPClient::_AddressToString(const uint8* data)
{
        BString target = inet_ntoa(*(in_addr*)data);
        return target;
}


/*static*/ BString
DHCPClient::_AddressToString(in_addr_t address)
{
        BString target = inet_ntoa(*(in_addr*)&address);
        return target;
}


status_t
DHCPClient::_SendMessage(int socket, dhcp_message& message,
        const BNetworkAddress& address) const
{
        message_type type = message.Type();
        BString text;
        text << dhcp_message::TypeToString(type);

        const uint8* requestAddress = message.FindOption(OPTION_REQUEST_IP_ADDRESS);
        if (type == DHCP_REQUEST && requestAddress != NULL)
                text << " for " << _AddressToString(requestAddress).String();

        syslog(LOG_DEBUG, "%s: Send %s to %s\n", Device(), text.String(),
                address.ToString().String());

        ssize_t bytesSent = sendto(socket, &message, message.Size(),
                address.IsBroadcast() ? MSG_BCAST : 0, address, address.Length());
        if (bytesSent < 0)
                return errno;

        return B_OK;
}


dhcp_state
DHCPClient::_CurrentState() const
{
        bigtime_t now = system_time();

        if (now > fLeaseTime || fStatus != B_OK)
                return fAssignedAddress == 0 ? INIT : INIT_REBOOT;
        if (now >= fRebindingTime)
                return REBINDING;
        if (now >= fRenewalTime)
                return RENEWING;
        return BOUND;
}


void
DHCPClient::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgLeaseTime:
                        Start();
                        break;

                default:
                        BHandler::MessageReceived(message);
                        break;
        }
}