Webserver+3d print
cyclone_tcp/dhcp/dhcp_client.c
- Committer:
- Sergunb
- Date:
- 2017-02-04
- Revision:
- 0:8918a71cdbe9
File content as of revision 0:8918a71cdbe9:
/** * @file dhcp_client.c * @brief DHCP client (Dynamic Host Configuration Protocol) * * @section License * * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. * * This file is part of CycloneTCP Open. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * @section Description * * The Dynamic Host Configuration Protocol is used to provide configuration * parameters to hosts. Refer to the following RFCs for complete details: * - RFC 2131: Dynamic Host Configuration Protocol * - RFC 2132: DHCP Options and BOOTP Vendor Extensions * - RFC 4039: Rapid Commit Option for the DHCP version 4 * * @author Oryx Embedded SARL (www.oryx-embedded.com) * @version 1.7.6 **/ //Switch to the appropriate trace level #define TRACE_LEVEL DHCP_TRACE_LEVEL //Dependencies #include "core/net.h" #include "dhcp/dhcp_client.h" #include "dhcp/dhcp_common.h" #include "dhcp/dhcp_debug.h" #include "mdns/mdns_responder.h" #include "date_time.h" #include "debug.h" //Check TCP/IP stack configuration #if (IPV4_SUPPORT == ENABLED && DHCP_CLIENT_SUPPORT == ENABLED) //Tick counter to handle periodic operations systime_t dhcpClientTickCounter; //Requested DHCP options const uint8_t dhcpOptionList[] = { DHCP_OPT_SUBNET_MASK, DHCP_OPT_ROUTER, DHCP_OPT_DNS_SERVER, DHCP_OPT_INTERFACE_MTU, DHCP_OPT_IP_ADDRESS_LEASE_TIME, DHCP_OPT_RENEWAL_TIME_VALUE, DHCP_OPT_REBINDING_TIME_VALUE }; /** * @brief Initialize settings with default values * @param[out] settings Structure that contains DHCP client settings **/ void dhcpClientGetDefaultSettings(DhcpClientSettings *settings) { //Use default interface settings->interface = netGetDefaultInterface(); //Use default host name strcpy(settings->hostname, ""); //Support for quick configuration using rapid commit settings->rapidCommit = FALSE; //Use the DNS servers provided by the DHCP server settings->manualDnsConfig = FALSE; //DHCP configuration timeout settings->timeout = 0; //DHCP configuration timeout event settings->timeoutEvent = NULL; //Link state change event settings->linkChangeEvent = NULL; //FSM state change event settings->stateChangeEvent = NULL; } /** * @brief DHCP client initialization * @param[in] context Pointer to the DHCP client context * @param[in] settings DHCP client specific settings * @return Error code **/ error_t dhcpClientInit(DhcpClientContext *context, const DhcpClientSettings *settings) { error_t error; size_t n; NetInterface *interface; //Debug message TRACE_INFO("Initializing DHCP client...\r\n"); //Ensure the parameters are valid if(context == NULL || settings == NULL) return ERROR_INVALID_PARAMETER; //A valid pointer to the interface being configured is required if(settings->interface == NULL) return ERROR_INVALID_PARAMETER; //Point to the underlying network interface interface = settings->interface; //Clear the DHCP client context memset(context, 0, sizeof(DhcpClientContext)); //Save user settings context->settings = *settings; //No DHCP host name defined? if(settings->hostname[0] == '\0') { //Use default host name n = strlen(interface->hostname); //Limit the length of the string n = MIN(n, DHCP_CLIENT_MAX_HOSTNAME_LEN); //Copy host name strncpy(context->settings.hostname, interface->hostname, n); //Properly terminate the string with a NULL character context->settings.hostname[n] = '\0'; } //Callback function to be called when a DHCP message is received error = udpAttachRxCallback(interface, DHCP_CLIENT_PORT, dhcpClientProcessMessage, context); //Failed to register callback function? if(error) return error; //DHCP client is currently suspended context->running = FALSE; //Initialize state machine context->state = DHCP_STATE_INIT; //Attach the DHCP client context to the network interface interface->dhcpClientContext = context; //Successful initialization return NO_ERROR; } /** * @brief Start DHCP client * @param[in] context Pointer to the DHCP client context * @return Error code **/ error_t dhcpClientStart(DhcpClientContext *context) { //Check parameter if(context == NULL) return ERROR_INVALID_PARAMETER; //Debug message TRACE_INFO("Starting DHCP client...\r\n"); //Get exclusive access osAcquireMutex(&netMutex); //Start DHCP client context->running = TRUE; //Initialize state machine context->state = DHCP_STATE_INIT; //Release exclusive access osReleaseMutex(&netMutex); //Successful processing return NO_ERROR; } /** * @brief Stop DHCP client * @param[in] context Pointer to the DHCP client context * @return Error code **/ error_t dhcpClientStop(DhcpClientContext *context) { //Check parameter if(context == NULL) return ERROR_INVALID_PARAMETER; //Debug message TRACE_INFO("Stopping DHCP client...\r\n"); //Get exclusive access osAcquireMutex(&netMutex); //Stop DHCP client context->running = FALSE; //Reinitialize state machine context->state = DHCP_STATE_INIT; //Release exclusive access osReleaseMutex(&netMutex); //Successful processing return NO_ERROR; } /** * @brief Retrieve current state * @param[in] context Pointer to the DHCP client context * @return Current DHCP client state **/ DhcpState dhcpClientGetState(DhcpClientContext *context) { DhcpState state; //Get exclusive access osAcquireMutex(&netMutex); //Get current state state = context->state; //Release exclusive access osReleaseMutex(&netMutex); //Return current state return state; } /** * @brief DHCP client timer handler * * This routine must be periodically called by the TCP/IP stack to * manage DHCP client operation * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientTick(DhcpClientContext *context) { //Make sure the DHCP client has been properly instantiated if(context == NULL) return; //DHCP client finite state machine switch(context->state) { //Process INIT state case DHCP_STATE_INIT: //This is the initialization state, where a client begins the process of //acquiring a lease. It also returns here when a lease ends, or when a //lease negotiation fails dhcpClientStateInit(context); break; //Process SELECTING state case DHCP_STATE_SELECTING: //The client is waiting to receive DHCPOFFER messages from one or more //DHCP servers, so it can choose one dhcpClientStateSelecting(context); break; //Process REQUESTING state case DHCP_STATE_REQUESTING: //The client is waiting to hear back from the server to which //it sent its request dhcpClientStateRequesting(context); break; //Process INIT REBOOT state case DHCP_STATE_INIT_REBOOT: //When a client that already has a valid lease starts up after a //power-down or reboot, it starts here instead of the INIT state dhcpClientStateInitReboot(context); break; //Process REBOOTING state case DHCP_STATE_REBOOTING: //A client that has rebooted with an assigned address is waiting for //a confirming reply from a server dhcpClientStateRebooting(context); break; //Process PROBING state case DHCP_STATE_PROBING: //The client probes the newly received address dhcpClientStateProbing(context); break; //Process BOUND state case DHCP_STATE_BOUND: //Client has a valid lease and is in its normal operating state dhcpClientStateBound(context); break; //Process RENEWING state case DHCP_STATE_RENEWING: //Client is trying to renew its lease. It regularly sends DHCPREQUEST messages with //the server that gave it its current lease specified, and waits for a reply dhcpClientStateRenewing(context); break; //Process REBINDING state case DHCP_STATE_REBINDING: //The client has failed to renew its lease with the server that originally granted it, //and now seeks a lease extension with any server that can hear it. It periodically sends //DHCPREQUEST messages with no server specified until it gets a reply or the lease ends dhcpClientStateRebinding(context); break; //Invalid state... default: //Switch to the INIT state context->state = DHCP_STATE_INIT; break; } } /** * @brief Callback function for link change event * @param[in] context Pointer to the DHCP client context **/ void dhcpClientLinkChangeEvent(DhcpClientContext *context) { NetInterface *interface; //Make sure the DHCP client has been properly instantiated if(context == NULL) return; //Point to the underlying network interface interface = context->settings.interface; //Check whether the DHCP client is running if(context->running) { //The host address is no longer valid interface->ipv4Context.addr = IPV4_UNSPECIFIED_ADDR; interface->ipv4Context.addrState = IPV4_ADDR_STATE_INVALID; #if (MDNS_RESPONDER_SUPPORT == ENABLED) //Restart mDNS probing process mdnsResponderStartProbing(interface->mdnsResponderContext); #endif //Clear subnet mask interface->ipv4Context.subnetMask = IPV4_UNSPECIFIED_ADDR; } //Check whether the client already has a valid lease if(context->state >= DHCP_STATE_INIT_REBOOT) { //Switch to the INIT-REBOOT state context->state = DHCP_STATE_INIT_REBOOT; } else { //Switch to the INIT state context->state = DHCP_STATE_INIT; } //Any registered callback? if(context->settings.linkChangeEvent != NULL) { //Release exclusive access osReleaseMutex(&netMutex); //Invoke user callback function context->settings.linkChangeEvent(context, interface, interface->linkState); //Get exclusive access osAcquireMutex(&netMutex); } } /** * @brief INIT state * * This is the initialization state, where a client begins the process of * acquiring a lease. It also returns here when a lease ends, or when a * lease negotiation fails * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateInit(DhcpClientContext *context) { systime_t delay; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Check whether the DHCP client is running if(context->running) { //Wait for the link to be up before starting DHCP configuration if(interface->linkState) { //The client should wait for a random time to //desynchronize the use of DHCP at startup delay = netGetRandRange(0, DHCP_CLIENT_INIT_DELAY); //Record the time at which the client started //the address acquisition process context->configStartTime = osGetSystemTime(); //Clear flag context->timeoutEventDone = FALSE; //Switch to the SELECTING state dhcpClientChangeState(context, DHCP_STATE_SELECTING, delay); } } } /** * @brief SELECTING state * * The client is waiting to receive DHCPOFFER messages from * one or more DHCP servers, so it can choose one * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateSelecting(DhcpClientContext *context) { systime_t time; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //Check retransmission counter if(context->retransmitCount == 0) { //A transaction identifier is used by the client to //match incoming DHCP messages with pending requests context->transactionId = netGetRand(); //Send a DHCPDISCOVER message dhcpClientSendDiscover(context); //Initial timeout value context->retransmitTimeout = DHCP_CLIENT_DISCOVER_INIT_RT; } else { //Send a DHCPDISCOVER message dhcpClientSendDiscover(context); //The timeout value is doubled for each subsequent retransmission context->retransmitTimeout *= 2; //Limit the timeout value to a maximum of 64 seconds if(context->retransmitTimeout > DHCP_CLIENT_DISCOVER_MAX_RT) context->retransmitTimeout = DHCP_CLIENT_DISCOVER_MAX_RT; } //Save the time at which the message was sent context->timestamp = time; //The timeout value should be randomized by the value of a uniform //number chosen from the range -1 to +1 context->timeout = context->retransmitTimeout + netGetRandRange(-DHCP_CLIENT_RAND_FACTOR, DHCP_CLIENT_RAND_FACTOR); //Increment retransmission counter context->retransmitCount++; } //Manage DHCP configuration timeout dhcpClientCheckTimeout(context); } /** * @brief REQUESTING state * * The client is waiting to hear back from the server * to which it sent its request * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateRequesting(DhcpClientContext *context) { systime_t time; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //Check retransmission counter if(context->retransmitCount == 0) { //A transaction identifier is used by the client to //match incoming DHCP messages with pending requests context->transactionId = netGetRand(); //Send a DHCPREQUEST message dhcpClientSendRequest(context); //Initial timeout value context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT; //Save the time at which the message was sent context->timestamp = time; //The timeout value should be randomized by the value of a uniform //number chosen from the range -1 to +1 context->timeout = context->retransmitTimeout + netGetRandRange(-DHCP_CLIENT_RAND_FACTOR, DHCP_CLIENT_RAND_FACTOR); //Increment retransmission counter context->retransmitCount++; } else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC) { //Send a DHCPREQUEST message dhcpClientSendRequest(context); //The timeout value is doubled for each subsequent retransmission context->retransmitTimeout *= 2; //Limit the timeout value to a maximum of 64 seconds if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT) context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT; //Save the time at which the message was sent context->timestamp = time; //The timeout value should be randomized by the value of a uniform //number chosen from the range -1 to +1 context->timeout = context->retransmitTimeout + netGetRandRange(-DHCP_CLIENT_RAND_FACTOR, DHCP_CLIENT_RAND_FACTOR); //Increment retransmission counter context->retransmitCount++; } else { //If the client does not receive a response within a reasonable //period of time, then it restarts the initialization procedure dhcpClientChangeState(context, DHCP_STATE_INIT, 0); } } //Manage DHCP configuration timeout dhcpClientCheckTimeout(context); } /** * @brief INIT-REBOOT state * * When a client that already has a valid lease starts up after a * power-down or reboot, it starts here instead of the INIT state * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateInitReboot(DhcpClientContext *context) { systime_t delay; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Check whether the DHCP client is running if(context->running) { //Wait for the link to be up before starting DHCP configuration if(interface->linkState) { //The client should wait for a random time to //desynchronize the use of DHCP at startup delay = netGetRandRange(0, DHCP_CLIENT_INIT_DELAY); //Record the time at which the client started //the address acquisition process context->configStartTime = osGetSystemTime(); //Clear flag context->timeoutEventDone = FALSE; //Switch to the REBOOTING state dhcpClientChangeState(context, DHCP_STATE_REBOOTING, delay); } } } /** * @brief REBOOTING state * * A client that has rebooted with an assigned address is * waiting for a confirming reply from a server * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateRebooting(DhcpClientContext *context) { systime_t time; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //Check retransmission counter if(context->retransmitCount == 0) { //A transaction identifier is used by the client to //match incoming DHCP messages with pending requests context->transactionId = netGetRand(); //Send a DHCPREQUEST message dhcpClientSendRequest(context); //Initial timeout value context->retransmitTimeout = DHCP_CLIENT_REQUEST_INIT_RT; //Save the time at which the message was sent context->timestamp = time; //The timeout value should be randomized by the value of a uniform //number chosen from the range -1 to +1 context->timeout = context->retransmitTimeout + netGetRandRange(-DHCP_CLIENT_RAND_FACTOR, DHCP_CLIENT_RAND_FACTOR); //Increment retransmission counter context->retransmitCount++; } else if(context->retransmitCount < DHCP_CLIENT_REQUEST_MAX_RC) { //Send a DHCPREQUEST message dhcpClientSendRequest(context); //The timeout value is doubled for each subsequent retransmission context->retransmitTimeout *= 2; //Limit the timeout value to a maximum of 64 seconds if(context->retransmitTimeout > DHCP_CLIENT_REQUEST_MAX_RT) context->retransmitTimeout = DHCP_CLIENT_REQUEST_MAX_RT; //Save the time at which the message was sent context->timestamp = time; //The timeout value should be randomized by the value of a uniform //number chosen from the range -1 to +1 context->timeout = context->retransmitTimeout + netGetRandRange(-DHCP_CLIENT_RAND_FACTOR, DHCP_CLIENT_RAND_FACTOR); //Increment retransmission counter context->retransmitCount++; } else { //If the client does not receive a response within a reasonable //period of time, then it restarts the initialization procedure dhcpClientChangeState(context, DHCP_STATE_INIT, 0); } } //Manage DHCP configuration timeout dhcpClientCheckTimeout(context); } /** * @brief PROBING state * * The client probes the newly received address * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateProbing(DhcpClientContext *context) { systime_t time; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //The address is already in use? if(interface->ipv4Context.addrConflict) { //If the client detects that the address is already in use, the //client must send a DHCPDECLINE message to the server and //restarts the configuration process dhcpClientSendDecline(context); //The client should wait a minimum of ten seconds before //restarting the configuration process to avoid excessive //network traffic in case of looping dhcpClientChangeState(context, DHCP_STATE_INIT, 0); } //Probing is on-going? else if(context->retransmitCount < DHCP_CLIENT_PROBE_NUM) { //Conflict detection is done using ARP probes arpSendProbe(interface, interface->ipv4Context.addr); //Save the time at which the packet was sent context->timestamp = time; //Delay until repeated probe context->timeout = DHCP_CLIENT_PROBE_DELAY; //Increment retransmission counter context->retransmitCount++; } //Probing is complete? else { //The use of the IPv4 address is now unrestricted interface->ipv4Context.addrState = IPV4_ADDR_STATE_VALID; #if (MDNS_RESPONDER_SUPPORT == ENABLED) //Restart mDNS probing process mdnsResponderStartProbing(interface->mdnsResponderContext); #endif //Dump current DHCP configuration for debugging purpose dhcpClientDumpConfig(context); //The client transitions to the BOUND state dhcpClientChangeState(context, DHCP_STATE_BOUND, 0); } } } /** * @brief BOUND state * * Client has a valid lease and is in its normal operating state * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateBound(DhcpClientContext *context) { systime_t t1; systime_t time; //Get current time time = osGetSystemTime(); //A client will never attempt to extend the lifetime //of the address when T1 set to 0xFFFFFFFF if(context->t1 != DHCP_INFINITE_TIME) { //Convert T1 to milliseconds if(context->t1 < (MAX_DELAY / 1000)) t1 = context->t1 * 1000; else t1 = MAX_DELAY; //Check the time elapsed since the lease was obtained if(timeCompare(time, context->leaseStartTime + t1) >= 0) { //Record the time at which the client started the address renewal process context->configStartTime = time; //Enter the RENEWING state dhcpClientChangeState(context, DHCP_STATE_RENEWING, 0); } } } /** * @brief RENEWING state * * Client is trying to renew its lease. It regularly sends * DHCPREQUEST messages with the server that gave it its current * lease specified, and waits for a reply * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateRenewing(DhcpClientContext *context) { systime_t t2; systime_t time; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //Convert T2 to milliseconds if(context->t2 < (MAX_DELAY / 1000)) t2 = context->t2 * 1000; else t2 = MAX_DELAY; //Check whether T2 timer has expired if(timeCompare(time, context->leaseStartTime + t2) < 0) { //First DHCPREQUEST message? if(context->retransmitCount == 0) { //A transaction identifier is used by the client to //match incoming DHCP messages with pending requests context->transactionId = netGetRand(); } //Send a DHCPREQUEST message dhcpClientSendRequest(context); //Save the time at which the message was sent context->timestamp = time; //Compute the remaining time until T2 expires context->timeout = context->leaseStartTime + t2 - time; //The client should wait one-half of the remaining time until T2, down to //a minimum of 60 seconds, before retransmitting the DHCPREQUEST message if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY)) context->timeout /= 2; //Increment retransmission counter context->retransmitCount++; } else { //If no DHCPACK arrives before time T2, the client moves to REBINDING dhcpClientChangeState(context, DHCP_STATE_REBINDING, 0); } } } /** * @brief REBINDING state * * The client has failed to renew its lease with the server that originally * granted it, and now seeks a lease extension with any server that can * hear it. It periodically sends DHCPREQUEST messages with no server specified * until it gets a reply or the lease ends * * @param[in] context Pointer to the DHCP client context **/ void dhcpClientStateRebinding(DhcpClientContext *context) { systime_t time; systime_t leaseTime; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Get current time time = osGetSystemTime(); //Check current time if(timeCompare(time, context->timestamp + context->timeout) >= 0) { //Convert the lease time to milliseconds if(context->leaseTime < (MAX_DELAY / 1000)) leaseTime = context->leaseTime * 1000; else leaseTime = MAX_DELAY; //Check whether the lease has expired if(timeCompare(time, context->leaseStartTime + leaseTime) < 0) { //First DHCPREQUEST message? if(context->retransmitCount == 0) { //A transaction identifier is used by the client to //match incoming DHCP messages with pending requests context->transactionId = netGetRand(); } //Send a DHCPREQUEST message dhcpClientSendRequest(context); //Save the time at which the message was sent context->timestamp = time; //Compute the remaining time until the lease expires context->timeout = context->leaseStartTime + leaseTime - time; //The client should wait one-half of the remaining lease time, down to a //minimum of 60 seconds, before retransmitting the DHCPREQUEST message if(context->timeout > (2 * DHCP_CLIENT_REQUEST_MIN_DELAY)) context->timeout /= 2; //Increment retransmission counter context->retransmitCount++; } else { //The host address is no longer valid... interface->ipv4Context.addr = IPV4_UNSPECIFIED_ADDR; interface->ipv4Context.addrState = IPV4_ADDR_STATE_INVALID; //Clear subnet mask interface->ipv4Context.subnetMask = IPV4_UNSPECIFIED_ADDR; #if (MDNS_RESPONDER_SUPPORT == ENABLED) //Restart mDNS probing process mdnsResponderStartProbing(interface->mdnsResponderContext); #endif //If the lease expires before the client receives //a DHCPACK, the client moves to INIT state dhcpClientChangeState(context, DHCP_STATE_INIT, 0); } } } /** * @brief Send DHCPDISCOVER message * @param[in] context Pointer to the DHCP client context * @return Error code **/ error_t dhcpClientSendDiscover(DhcpClientContext *context) { error_t error; size_t length; size_t offset; NetBuffer *buffer; NetInterface *interface; DhcpMessage *message; IpAddr destIpAddr; //DHCP message type const uint8_t messageType = DHCP_MESSAGE_TYPE_DISCOVER; //Point to the underlying network interface interface = context->settings.interface; //Allocate a memory buffer to hold the DHCP message buffer = udpAllocBuffer(DHCP_MIN_MSG_SIZE, &offset); //Failed to allocate buffer? if(buffer == NULL) return ERROR_OUT_OF_MEMORY; //Point to the beginning of the DHCP message message = netBufferAt(buffer, offset); //Clear memory buffer contents memset(message, 0, DHCP_MIN_MSG_SIZE); //Format DHCPDISCOVER message message->op = DHCP_OPCODE_BOOTREQUEST; message->htype = DHCP_HARDWARE_TYPE_ETH; message->hlen = sizeof(MacAddr); message->xid = htonl(context->transactionId); message->secs = dhcpClientComputeElapsedTime(context); message->flags = HTONS(DHCP_FLAG_BROADCAST); message->ciaddr = IPV4_UNSPECIFIED_ADDR; message->chaddr = interface->macAddr; //Write magic cookie before setting any option message->magicCookie = HTONL(DHCP_MAGIC_COOKIE); //Properly terminate options field message->options[0] = DHCP_OPT_END; //DHCP Message Type option dhcpAddOption(message, DHCP_OPT_DHCP_MESSAGE_TYPE, &messageType, sizeof(messageType)); //Retrieve the length of the host name length = strlen(context->settings.hostname); //Any host name defined? if(length > 0) { //The Host Name option specifies the name of the client dhcpAddOption(message, DHCP_OPT_HOST_NAME, context->settings.hostname, length); } //Check whether rapid commit is enabled if(context->settings.rapidCommit) { //Include the Rapid Commit option if the client is prepared //to perform the DHCPDISCOVER-DHCPACK message exchange dhcpAddOption(message, DHCP_OPT_RAPID_COMMIT, NULL, 0); } //Set destination IP address destIpAddr.length = sizeof(Ipv4Addr); destIpAddr.ipv4Addr = IPV4_BROADCAST_ADDR; //Debug message TRACE_DEBUG("\r\n%s: Sending DHCP message (%" PRIuSIZE " bytes)...\r\n", formatSystemTime(osGetSystemTime(), NULL), DHCP_MIN_MSG_SIZE); //Dump the contents of the message for debugging purpose dhcpDumpMessage(message, DHCP_MIN_MSG_SIZE); //Broadcast DHCPDISCOVER message error = udpSendDatagramEx(interface, DHCP_CLIENT_PORT, &destIpAddr, DHCP_SERVER_PORT, buffer, offset, IPV4_DEFAULT_TTL); //Free previously allocated memory netBufferFree(buffer); //Return status code return error; } /** * @brief Send DHCPREQUEST message * @param[in] context Pointer to the DHCP client context * @return Error code **/ error_t dhcpClientSendRequest(DhcpClientContext *context) { error_t error; size_t length; size_t offset; NetBuffer *buffer; NetInterface *interface; DhcpMessage *message; IpAddr destIpAddr; //DHCP message type const uint8_t messageType = DHCP_MESSAGE_TYPE_REQUEST; //Point to the underlying network interface interface = context->settings.interface; //Allocate a memory buffer to hold the DHCP message buffer = udpAllocBuffer(DHCP_MIN_MSG_SIZE, &offset); //Failed to allocate buffer? if(buffer == NULL) return ERROR_OUT_OF_MEMORY; //Point to the beginning of the DHCP message message = netBufferAt(buffer, offset); //Clear memory buffer contents memset(message, 0, DHCP_MIN_MSG_SIZE); //Format DHCPREQUEST message message->op = DHCP_OPCODE_BOOTREQUEST; message->htype = DHCP_HARDWARE_TYPE_ETH; message->hlen = sizeof(MacAddr); message->xid = htonl(context->transactionId); message->secs = dhcpClientComputeElapsedTime(context); //The client IP address must be included if the client //is fully configured and can respond to ARP requests if(context->state == DHCP_STATE_RENEWING || context->state == DHCP_STATE_REBINDING) { message->flags = 0; message->ciaddr = interface->ipv4Context.addr; } else { message->flags = HTONS(DHCP_FLAG_BROADCAST); message->ciaddr = IPV4_UNSPECIFIED_ADDR; } //Client hardware address message->chaddr = interface->macAddr; //Write magic cookie before setting any option message->magicCookie = HTONL(DHCP_MAGIC_COOKIE); //Properly terminate options field message->options[0] = DHCP_OPT_END; //DHCP Message Type option dhcpAddOption(message, DHCP_OPT_DHCP_MESSAGE_TYPE, &messageType, sizeof(messageType)); //Retrieve the length of the host name length = strlen(context->settings.hostname); //Any host name defined? if(length > 0) { //The Host Name option specifies the name of the client dhcpAddOption(message, DHCP_OPT_HOST_NAME, context->settings.hostname, length); } //Server Identifier option if(context->state == DHCP_STATE_REQUESTING) { dhcpAddOption(message, DHCP_OPT_SERVER_IDENTIFIER, &context->serverIpAddr, sizeof(Ipv4Addr)); } //Requested IP Address option if(context->state == DHCP_STATE_REQUESTING || context->state == DHCP_STATE_REBOOTING) { dhcpAddOption(message, DHCP_OPT_REQUESTED_IP_ADDRESS, &context->requestedIpAddr, sizeof(Ipv4Addr)); } //Parameter Request List option dhcpAddOption(message, DHCP_OPT_PARAM_REQUEST_LIST, dhcpOptionList, sizeof(dhcpOptionList)); //IP address is being renewed? if(context->state == DHCP_STATE_RENEWING) { //The client transmits the message directly to the //server that initially granted the lease destIpAddr.length = sizeof(Ipv4Addr); destIpAddr.ipv4Addr = context->serverIpAddr; } else { //Broadcast the message destIpAddr.length = sizeof(Ipv4Addr); destIpAddr.ipv4Addr = IPV4_BROADCAST_ADDR; } //Debug message TRACE_DEBUG("\r\n%s: Sending DHCP message (%" PRIuSIZE " bytes)...\r\n", formatSystemTime(osGetSystemTime(), NULL), DHCP_MIN_MSG_SIZE); //Dump the contents of the message for debugging purpose dhcpDumpMessage(message, DHCP_MIN_MSG_SIZE); //Send DHCPREQUEST message error = udpSendDatagramEx(interface, DHCP_CLIENT_PORT, &destIpAddr, DHCP_SERVER_PORT, buffer, offset, IPV4_DEFAULT_TTL); //Free previously allocated memory netBufferFree(buffer); //Return status code return error; } /** * @brief Send DHCPDECLINE message * @param[in] context Pointer to the DHCP client context * @return Error code **/ error_t dhcpClientSendDecline(DhcpClientContext *context) { error_t error; size_t offset; NetBuffer *buffer; NetInterface *interface; DhcpMessage *message; IpAddr destIpAddr; //DHCP message type const uint8_t messageType = DHCP_MESSAGE_TYPE_DECLINE; //Point to the underlying network interface interface = context->settings.interface; //Allocate a memory buffer to hold the DHCP message buffer = udpAllocBuffer(DHCP_MIN_MSG_SIZE, &offset); //Failed to allocate buffer? if(buffer == NULL) return ERROR_OUT_OF_MEMORY; //Point to the beginning of the DHCP message message = netBufferAt(buffer, offset); //Clear memory buffer contents memset(message, 0, DHCP_MIN_MSG_SIZE); //Format DHCPDECLINE message message->op = DHCP_OPCODE_BOOTREQUEST; message->htype = DHCP_HARDWARE_TYPE_ETH; message->hlen = sizeof(MacAddr); message->xid = htonl(context->transactionId); message->secs = 0; message->flags = 0; message->ciaddr = IPV4_UNSPECIFIED_ADDR; message->chaddr = interface->macAddr; //Write magic cookie before setting any option message->magicCookie = HTONL(DHCP_MAGIC_COOKIE); //Properly terminate options field message->options[0] = DHCP_OPT_END; //DHCP Message Type option dhcpAddOption(message, DHCP_OPT_DHCP_MESSAGE_TYPE, &messageType, sizeof(messageType)); //Server Identifier option dhcpAddOption(message, DHCP_OPT_SERVER_IDENTIFIER, &context->serverIpAddr, sizeof(Ipv4Addr)); //Requested IP Address option dhcpAddOption(message, DHCP_OPT_REQUESTED_IP_ADDRESS, &context->requestedIpAddr, sizeof(Ipv4Addr)); //Set destination IP address destIpAddr.length = sizeof(Ipv4Addr); destIpAddr.ipv4Addr = IPV4_BROADCAST_ADDR; //Debug message TRACE_DEBUG("\r\n%s: Sending DHCP message (%" PRIuSIZE " bytes)...\r\n", formatSystemTime(osGetSystemTime(), NULL), DHCP_MIN_MSG_SIZE); //Dump the contents of the message for debugging purpose dhcpDumpMessage(message, DHCP_MIN_MSG_SIZE); //Broadcast DHCPDECLINE message error = udpSendDatagramEx(interface, DHCP_CLIENT_PORT, &destIpAddr, DHCP_SERVER_PORT, buffer, offset, IPV4_DEFAULT_TTL); //Free previously allocated memory netBufferFree(buffer); //Return status code return error; } /** * @brief Process incoming DHCP message * @param[in] interface Underlying network interface * @param[in] pseudoHeader UDP pseudo header * @param[in] udpHeader UDP header * @param[in] buffer Multi-part buffer containing the incoming DHCP message * @param[in] offset Offset to the first byte of the DHCP message * @param[in] params Pointer to the DHCP client context **/ void dhcpClientProcessMessage(NetInterface *interface, const IpPseudoHeader *pseudoHeader, const UdpHeader *udpHeader, const NetBuffer *buffer, size_t offset, void *params) { size_t length; DhcpClientContext *context; DhcpMessage *message; DhcpOption *option; //Point to the DHCP client context context = (DhcpClientContext *) params; //Retrieve the length of the DHCP message length = netBufferGetLength(buffer) - offset; //Make sure the DHCP message is valid if(length < sizeof(DhcpMessage)) return; if(length > DHCP_MAX_MSG_SIZE) return; //Point to the beginning of the DHCP message message = netBufferAt(buffer, offset); //Sanity check if(message == NULL) return; //Debug message TRACE_DEBUG("\r\n%s: DHCP message received (%" PRIuSIZE " bytes)...\r\n", formatSystemTime(osGetSystemTime(), NULL), length); //Dump the contents of the message for debugging purpose dhcpDumpMessage(message, length); //The DHCP server shall respond with a BOOTREPLY opcode if(message->op != DHCP_OPCODE_BOOTREPLY) return; //Enforce hardware type if(message->htype != DHCP_HARDWARE_TYPE_ETH) return; //Check the length of the hardware address if(message->hlen != sizeof(MacAddr)) return; //Check magic cookie if(message->magicCookie != HTONL(DHCP_MAGIC_COOKIE)) return; //The DHCP Message Type option must be included in every DHCP message option = dhcpGetOption(message, length, DHCP_OPT_DHCP_MESSAGE_TYPE); //Failed to retrieve the Message Type option? if(option == NULL || option->length != 1) return; //Check message type switch(option->value[0]) { case DHCP_MESSAGE_TYPE_OFFER: //Parse DHCPOFFER message dhcpClientParseOffer(context, message, length); break; case DHCP_MESSAGE_TYPE_ACK: //Parse DHCPACK message dhcpClientParseAck(context, message, length); break; case DHCP_MESSAGE_TYPE_NAK: //Parse DHCPNAK message dhcpClientParseNak(context, message, length); break; default: //Silently drop incoming message break; } } /** * @brief Parse DHCPOFFER message * @param[in] context Pointer to the DHCP client context * @param[in] message Pointer to the incoming DHCP message * @param[in] length Length of the incoming message to parse **/ void dhcpClientParseOffer(DhcpClientContext *context, const DhcpMessage *message, size_t length) { DhcpOption *serverIdOption; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Discard any received packet that does not match the transaction ID if(ntohl(message->xid) != context->transactionId) return; //Make sure the IP address offered to the client is valid if(message->yiaddr == IPV4_UNSPECIFIED_ADDR) return; //Check MAC address if(!macCompAddr(&message->chaddr, &interface->macAddr)) return; //Make sure that the DHCPOFFER message is received in response to //a DHCPDISCOVER message if(context->state != DHCP_STATE_SELECTING) return; //A DHCP server always returns its own address in the Server Identifier option serverIdOption = dhcpGetOption(message, length, DHCP_OPT_SERVER_IDENTIFIER); //Failed to retrieve the Server Identifier option? if(serverIdOption == NULL || serverIdOption->length != 4) return; //Record the IP address of the DHCP server ipv4CopyAddr(&context->serverIpAddr, serverIdOption->value); //Record the IP address offered to the client context->requestedIpAddr = message->yiaddr; //Switch to the REQUESTING state dhcpClientChangeState(context, DHCP_STATE_REQUESTING, 0); } /** * @brief Parse DHCPACK message * @param[in] context Pointer to the DHCP client context * @param[in] message Pointer to the incoming DHCP message * @param[in] length Length of the incoming message to parse * @return Error code **/ void dhcpClientParseAck(DhcpClientContext *context, const DhcpMessage *message, size_t length) { uint_t i; uint_t n; DhcpOption *option; DhcpOption *serverIdOption; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Discard any received packet that does not match the transaction ID if(ntohl(message->xid) != context->transactionId) return; //Make sure the IP address assigned to the client is valid if(message->yiaddr == IPV4_UNSPECIFIED_ADDR) return; //Check MAC address if(!macCompAddr(&message->chaddr, &interface->macAddr)) return; //A DHCP server always returns its own address in the Server Identifier option serverIdOption = dhcpGetOption(message, length, DHCP_OPT_SERVER_IDENTIFIER); //Failed to retrieve the Server Identifier option? if(serverIdOption == NULL || serverIdOption->length != 4) return; //Check current state if(context->state == DHCP_STATE_SELECTING) { //A DHCPACK message is not acceptable when rapid commit is disallowed if(!context->settings.rapidCommit) return; //Search for the Rapid Commit option option = dhcpGetOption(message, length, DHCP_OPT_RAPID_COMMIT); //A server must include this option in a DHCPACK message sent //in a response to a DHCPDISCOVER message when completing the //DHCPDISCOVER-DHCPACK message exchange if(option == NULL || option->length != 0) return; } else if(context->state == DHCP_STATE_REQUESTING || context->state == DHCP_STATE_RENEWING) { //Check the server identifier if(!ipv4CompAddr(serverIdOption->value, &context->serverIpAddr)) return; } else if(context->state == DHCP_STATE_REBOOTING || context->state == DHCP_STATE_REBINDING) { //Do not check the server identifier } else { //Silently discard the DHCPACK message return; } //Retrieve IP Address Lease Time option option = dhcpGetOption(message, length, DHCP_OPT_IP_ADDRESS_LEASE_TIME); //Failed to retrieve specified option? if(option == NULL || option->length != 4) return; //Record the lease time context->leaseTime = LOAD32BE(option->value); //Retrieve Renewal Time Value option option = dhcpGetOption(message, length, DHCP_OPT_RENEWAL_TIME_VALUE); //Specified option found? if(option != NULL && option->length == 4) { //This option specifies the time interval from address assignment //until the client transitions to the RENEWING state context->t1 = LOAD32BE(option->value); } else if(context->leaseTime != DHCP_INFINITE_TIME) { //By default, T1 is set to 50% of the lease time context->t1 = context->leaseTime / 2; } else { //Infinite lease context->t1 = DHCP_INFINITE_TIME; } //Retrieve Rebinding Time value option option = dhcpGetOption(message, length, DHCP_OPT_REBINDING_TIME_VALUE); //Specified option found? if(option != NULL && option->length == 4) { //This option specifies the time interval from address assignment //until the client transitions to the REBINDING state context->t2 = LOAD32BE(option->value); } else if(context->leaseTime != DHCP_INFINITE_TIME) { //By default, T2 is set to 87.5% of the lease time context->t2 = context->leaseTime * 7 / 8; } else { //Infinite lease context->t2 = DHCP_INFINITE_TIME; } //Retrieve Subnet Mask option option = dhcpGetOption(message, length, DHCP_OPT_SUBNET_MASK); //The specified option has been found? if(option != NULL && option->length == sizeof(Ipv4Addr)) { //Record subnet mask ipv4CopyAddr(&interface->ipv4Context.subnetMask, option->value); } //Retrieve Router option option = dhcpGetOption(message, length, DHCP_OPT_ROUTER); //The specified option has been found? if(option != NULL && !(option->length % sizeof(Ipv4Addr))) { //Save default gateway if(option->length >= sizeof(Ipv4Addr)) ipv4CopyAddr(&interface->ipv4Context.defaultGateway, option->value); } //Use the DNS servers provided by the DHCP server? if(!context->settings.manualDnsConfig) { //Retrieve DNS Server option option = dhcpGetOption(message, length, DHCP_OPT_DNS_SERVER); //The specified option has been found? if(option != NULL && !(option->length % sizeof(Ipv4Addr))) { //Get the number of addresses provided in the response n = option->length / sizeof(Ipv4Addr); //Loop through the list of addresses for(i = 0; i < n && i < IPV4_DNS_SERVER_LIST_SIZE; i++) { //Record DNS server address ipv4CopyAddr(&interface->ipv4Context.dnsServerList[i], option->value + i * sizeof(Ipv4Addr)); } } } //Retrieve MTU option option = dhcpGetOption(message, length, DHCP_OPT_INTERFACE_MTU); //The specified option has been found? if(option != NULL && option->length == 2) { //This option specifies the MTU to use on this interface n = LOAD16BE(option->value); //Make sure that the option's value is acceptable if(n >= IPV4_MINIMUM_MTU && n <= interface->nicDriver->mtu) { //Set the MTU to be used on the interface interface->ipv4Context.linkMtu = n; } } //Record the IP address of the DHCP server ipv4CopyAddr(&context->serverIpAddr, serverIdOption->value); //Record the IP address assigned to the client context->requestedIpAddr = message->yiaddr; //Save the time a which the lease was obtained context->leaseStartTime = osGetSystemTime(); //Check current state if(context->state == DHCP_STATE_REQUESTING || context->state == DHCP_STATE_REBOOTING) { //Use the IP address as a tentative address interface->ipv4Context.addr = message->yiaddr; interface->ipv4Context.addrState = IPV4_ADDR_STATE_TENTATIVE; //Clear conflict flag interface->ipv4Context.addrConflict = FALSE; //The client should probe the newly received address dhcpClientChangeState(context, DHCP_STATE_PROBING, 0); } else { //Assign the IP address to the client interface->ipv4Context.addr = message->yiaddr; interface->ipv4Context.addrState = IPV4_ADDR_STATE_VALID; #if (MDNS_RESPONDER_SUPPORT == ENABLED) //Restart mDNS probing process mdnsResponderStartProbing(interface->mdnsResponderContext); #endif //The client transitions to the BOUND state dhcpClientChangeState(context, DHCP_STATE_BOUND, 0); } } /** * @brief Parse DHCPNAK message * @param[in] context Pointer to the DHCP client context * @param[in] message Pointer to the incoming DHCP message * @param[in] length Length of the incoming message to parse * @return Error code **/ void dhcpClientParseNak(DhcpClientContext *context, const DhcpMessage *message, size_t length) { DhcpOption *serverIdOption; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Discard any received packet that does not match the transaction ID if(ntohl(message->xid) != context->transactionId) return; //Check MAC address if(!macCompAddr(&message->chaddr, &interface->macAddr)) return; //A DHCP server always returns its own address in the Server Identifier option serverIdOption = dhcpGetOption(message, length, DHCP_OPT_SERVER_IDENTIFIER); //Failed to retrieve the Server Identifier option? if(serverIdOption == NULL || serverIdOption->length != 4) return; //Check current state if(context->state == DHCP_STATE_REQUESTING || context->state == DHCP_STATE_RENEWING) { //Check the server identifier if(!ipv4CompAddr(serverIdOption->value, &context->serverIpAddr)) return; } else if(context->state == DHCP_STATE_REBOOTING || context->state == DHCP_STATE_REBINDING) { //Do not check the server identifier } else { //Silently discard the DHCPNAK message return; } //The host address is no longer appropriate for the link interface->ipv4Context.addr = IPV4_UNSPECIFIED_ADDR; interface->ipv4Context.addrState = IPV4_ADDR_STATE_INVALID; //Clear subnet mask interface->ipv4Context.subnetMask = IPV4_UNSPECIFIED_ADDR; #if (MDNS_RESPONDER_SUPPORT == ENABLED) //Restart mDNS probing process mdnsResponderStartProbing(interface->mdnsResponderContext); #endif //Restart DHCP configuration dhcpClientChangeState(context, DHCP_STATE_INIT, 0); } /** * @brief Manage DHCP configuration timeout * @param[in] context Pointer to the DHCP client context **/ void dhcpClientCheckTimeout(DhcpClientContext *context) { systime_t time; NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Get current time time = osGetSystemTime(); //Any registered callback? if(context->settings.timeoutEvent != NULL) { //DHCP configuration timeout? if(timeCompare(time, context->configStartTime + context->settings.timeout) >= 0) { //Ensure the callback function is only called once if(!context->timeoutEventDone) { //Release exclusive access osReleaseMutex(&netMutex); //Invoke user callback function context->settings.timeoutEvent(context, interface); //Get exclusive access osAcquireMutex(&netMutex); //Set flag context->timeoutEventDone = TRUE; } } } } /** * @brief Compute the appropriate secs field * * Compute the number of seconds elapsed since the client began * address acquisition or renewal process * * @param[in] context Pointer to the DHCP client context * @return The elapsed time expressed in seconds **/ uint16_t dhcpClientComputeElapsedTime(DhcpClientContext *context) { systime_t time; //Compute the time elapsed since the DHCP configuration process started time = (osGetSystemTime() - context->configStartTime) / 1000; //The value 0xFFFF is used to represent any elapsed time values //greater than the largest time value that can be represented time = MIN(time, 0xFFFF); //Convert the 16-bit value to network byte order return htons(time); } /** * @brief Update DHCP FSM state * @param[in] context Pointer to the DHCP client context * @param[in] newState New DHCP state to switch to * @param[in] delay Initial delay **/ void dhcpClientChangeState(DhcpClientContext *context, DhcpState newState, systime_t delay) { systime_t time; //Get current time time = osGetSystemTime(); #if (DHCP_TRACE_LEVEL >= TRACE_LEVEL_INFO) //Sanity check if(newState <= DHCP_STATE_REBINDING) { //DHCP FSM states static const char_t *stateLabel[] = { "INIT", "SELECTING", "REQUESTING", "INIT-REBOOT", "REBOOTING", "PROBING", "BOUND", "RENEWING", "REBINDING" }; //Debug message TRACE_INFO("%s: DHCP client %s state\r\n", formatSystemTime(time, NULL), stateLabel[newState]); } #endif //Set time stamp context->timestamp = time; //Set initial delay context->timeout = delay; //Reset retransmission counter context->retransmitCount = 0; //Switch to the new state context->state = newState; //Any registered callback? if(context->settings.stateChangeEvent != NULL) { NetInterface *interface; //Point to the underlying network interface interface = context->settings.interface; //Release exclusive access osReleaseMutex(&netMutex); //Invoke user callback function context->settings.stateChangeEvent(context, interface, newState); //Get exclusive access osAcquireMutex(&netMutex); } } /** * @brief Dump DHCP configuration for debugging purpose * @param[in] context Pointer to the DHCP client context **/ void dhcpClientDumpConfig(DhcpClientContext *context) { #if (DHCP_TRACE_LEVEL >= TRACE_LEVEL_INFO) uint_t i; NetInterface *interface; Ipv4Context *ipv4Context; //Point to the underlying network interface interface = context->settings.interface; //Point to the IPv4 context ipv4Context = &interface->ipv4Context; //Debug message TRACE_INFO("\r\n"); TRACE_INFO("DHCP configuration:\r\n"); //Lease start time TRACE_INFO(" Lease Start Time = %s\r\n", formatSystemTime(context->leaseStartTime, NULL)); //Lease time TRACE_INFO(" Lease Time = %" PRIu32 "s\r\n", context->leaseTime); //Renewal time TRACE_INFO(" T1 = %" PRIu32 "s\r\n", context->t1); //Rebinding time TRACE_INFO(" T2 = %" PRIu32 "s\r\n", context->t2); //Host address TRACE_INFO(" IPv4 Address = %s\r\n", ipv4AddrToString(ipv4Context->addr, NULL)); //Subnet mask TRACE_INFO(" Subnet Mask = %s\r\n", ipv4AddrToString(ipv4Context->subnetMask, NULL)); //Default gateway TRACE_INFO(" Default Gateway = %s\r\n", ipv4AddrToString(ipv4Context->defaultGateway, NULL)); //DNS servers for(i = 0; i < IPV4_DNS_SERVER_LIST_SIZE; i++) { TRACE_INFO(" DNS Server %u = %s\r\n", i + 1, ipv4AddrToString(ipv4Context->dnsServerList[i], NULL)); } //Maximum transmit unit TRACE_INFO(" MTU = %" PRIuSIZE "\r\n", interface->ipv4Context.linkMtu); TRACE_INFO("\r\n"); #endif } #endif