mbed OS5
Fork of UIPEthernet by
Diff: Dhcp.cpp
- Revision:
- 0:5350a66d5279
- Child:
- 2:049ce85163c5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Dhcp.cpp Mon Sep 15 11:12:30 2014 +0000 @@ -0,0 +1,538 @@ +// DHCP Library v0.3 - April 25, 2009 +// Author: Jordan Terrell - blog.jordanterrell.com +#include <string.h> +#include <stdlib.h> +#include "Dhcp.h" +#include "util.h" +#include "uip_clock.h" + +/** + * @brief + * @note + * @param + * @retval + */ +int DhcpClass::beginWithDHCP(uint8_t* mac, unsigned long timeout, unsigned long responseTimeout) { + _dhcpLeaseTime = 0; + _dhcpT1 = 0; + _dhcpT2 = 0; + _lastCheck = 0; + _timeout = timeout; + _responseTimeout = responseTimeout; + + // zero out _dhcpMacAddr + memset(_dhcpMacAddr, 0, 6); + reset_DHCP_lease(); + + memcpy((void*)_dhcpMacAddr, (void*)mac, 6); + _dhcp_state = STATE_DHCP_START; + return request_DHCP_lease(); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void DhcpClass::reset_DHCP_lease(void) { + + // zero out _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp + + memset(_dhcpLocalIp, 0, 20); +} + +//return:0 on error, 1 if request is sent and response is received +int DhcpClass::request_DHCP_lease(void) { + uint8_t messageType = 0; + + // Pick an initial transaction ID + + _dhcpTransactionId = (rand() * 2000UL) / RAND_MAX + 1; + _dhcpInitialTransactionId = _dhcpTransactionId; + + _dhcpUdpSocket.stop(); + if(_dhcpUdpSocket.begin(DHCP_CLIENT_PORT) == 0) { + + // Couldn't get a socket + return 0; + } + + presend_DHCP(); + + int result = 0; + + unsigned long startTime = clock_time(); + + while(_dhcp_state != STATE_DHCP_LEASED) { + if(_dhcp_state == STATE_DHCP_START) { + _dhcpTransactionId++; + + send_DHCP_MESSAGE(DHCP_DISCOVER, ((clock_time() - startTime) / 1000)); + _dhcp_state = STATE_DHCP_DISCOVER; + } + else + if(_dhcp_state == STATE_DHCP_REREQUEST) { + _dhcpTransactionId++; + send_DHCP_MESSAGE(DHCP_REQUEST, ((clock_time() - startTime) / 1000)); + _dhcp_state = STATE_DHCP_REQUEST; + } + else + if(_dhcp_state == STATE_DHCP_DISCOVER) { + uint32_t respId; + messageType = parseDHCPResponse(_responseTimeout, respId); + if(messageType == DHCP_OFFER) { + + // We'll use the transaction ID that the offer came with, + // rather than the one we were up to + _dhcpTransactionId = respId; + send_DHCP_MESSAGE(DHCP_REQUEST, ((clock_time() - startTime) / 1000)); + _dhcp_state = STATE_DHCP_REQUEST; + } + } + else + if(_dhcp_state == STATE_DHCP_REQUEST) { + uint32_t respId; + messageType = parseDHCPResponse(_responseTimeout, respId); + if(messageType == DHCP_ACK) { + _dhcp_state = STATE_DHCP_LEASED; + result = 1; + + //use default lease time if we didn't get it + if(_dhcpLeaseTime == 0) { + _dhcpLeaseTime = DEFAULT_LEASE; + } + + //calculate T1 & T2 if we didn't get it + if(_dhcpT1 == 0) { + + //T1 should be 50% of _dhcpLeaseTime + _dhcpT1 = _dhcpLeaseTime >> 1; + } + + if(_dhcpT2 == 0) { + + //T2 should be 87.5% (7/8ths) of _dhcpLeaseTime + _dhcpT2 = _dhcpT1 << 1; + } + + _renewInSec = _dhcpT1; + _rebindInSec = _dhcpT2; + } + else + if(messageType == DHCP_NAK) + _dhcp_state = STATE_DHCP_START; + } + + if(messageType == 255) { + messageType = 0; + _dhcp_state = STATE_DHCP_START; + } + + if(result != 1 && ((clock_time() - startTime) > _timeout)) + break; + } + + // We're done with the socket now + _dhcpUdpSocket.stop(); + _dhcpTransactionId++; + + return result; +} + +/** + * @brief + * @note + * @param + * @retval + */ +void DhcpClass::presend_DHCP(void) +{ } + +/** + * @brief + * @note + * @param + * @retval + */ +void DhcpClass::send_DHCP_MESSAGE(uint8_t messageType, uint16_t secondsElapsed) { + uint8_t buffer[32]; + memset(buffer, 0, 32); + + IPAddress dest_addr(255, 255, 255, 255); // Broadcast address + + if(-1 == _dhcpUdpSocket.beginPacket(dest_addr, DHCP_SERVER_PORT)) { + + // FIXME Need to return errors + return; + } + + buffer[0] = DHCP_BOOTREQUEST; // op + buffer[1] = DHCP_HTYPE10MB; // htype + buffer[2] = DHCP_HLENETHERNET; // hlen + buffer[3] = DHCP_HOPS; // hops + + // xid + unsigned long xid = htonl(_dhcpTransactionId); + memcpy(buffer + 4, &(xid), 4); + + // 8, 9 - seconds elapsed + buffer[8] = ((secondsElapsed & 0xff00) >> 8); + buffer[9] = (secondsElapsed & 0x00ff); + + // flags + unsigned short flags = htons(DHCP_FLAGSBROADCAST); + memcpy(buffer + 10, &(flags), 2); + + // ciaddr: already zeroed + // yiaddr: already zeroed + // siaddr: already zeroed + // giaddr: already zeroed + //put data in W5100 transmit buffer + _dhcpUdpSocket.write(buffer, 28); + + memset(buffer, 0, 32); // clear local buffer + memcpy(buffer, _dhcpMacAddr, 6); // chaddr + + //put data in W5100 transmit buffer + _dhcpUdpSocket.write(buffer, 16); + + memset(buffer, 0, 32); // clear local buffer + + // leave zeroed out for sname && file + // put in W5100 transmit buffer x 6 (192 bytes) + for(int i = 0; i < 6; i++) { + _dhcpUdpSocket.write(buffer, 32); + } + + // OPT - Magic Cookie + buffer[0] = (uint8_t) ((MAGIC_COOKIE >> 24) & 0xFF); + buffer[1] = (uint8_t) ((MAGIC_COOKIE >> 16) & 0xFF); + buffer[2] = (uint8_t) ((MAGIC_COOKIE >> 8) & 0xFF); + buffer[3] = (uint8_t) (MAGIC_COOKIE & 0xFF); + + // OPT - message type + buffer[4] = dhcpMessageType; + buffer[5] = 0x01; + buffer[6] = messageType; //DHCP_REQUEST; + + // OPT - client identifier + buffer[7] = dhcpClientIdentifier; + buffer[8] = 0x07; + buffer[9] = 0x01; + memcpy(buffer + 10, _dhcpMacAddr, 6); + + // OPT - host name + buffer[16] = hostName; + buffer[17] = strlen(HOST_NAME) + 6; // length of hostname + last 3 bytes of mac address + strcpy((char*) &(buffer[18]), HOST_NAME); + + printByte((char*) &(buffer[24]), _dhcpMacAddr[3]); + printByte((char*) &(buffer[26]), _dhcpMacAddr[4]); + printByte((char*) &(buffer[28]), _dhcpMacAddr[5]); + + //put data in W5100 transmit buffer + _dhcpUdpSocket.write(buffer, 30); + + if(messageType == DHCP_REQUEST) { + buffer[0] = dhcpRequestedIPaddr; + buffer[1] = 0x04; + buffer[2] = _dhcpLocalIp[0]; + buffer[3] = _dhcpLocalIp[1]; + buffer[4] = _dhcpLocalIp[2]; + buffer[5] = _dhcpLocalIp[3]; + + buffer[6] = dhcpServerIdentifier; + buffer[7] = 0x04; + buffer[8] = _dhcpDhcpServerIp[0]; + buffer[9] = _dhcpDhcpServerIp[1]; + buffer[10] = _dhcpDhcpServerIp[2]; + buffer[11] = _dhcpDhcpServerIp[3]; + + //put data in W5100 transmit buffer + _dhcpUdpSocket.write(buffer, 12); + } + + buffer[0] = dhcpParamRequest; + buffer[1] = 0x06; + buffer[2] = subnetMask; + buffer[3] = routersOnSubnet; + buffer[4] = dns; + buffer[5] = domainName; + buffer[6] = dhcpT1value; + buffer[7] = dhcpT2value; + buffer[8] = endOption; + + //put data in W5100 transmit buffer + _dhcpUdpSocket.write(buffer, 9); + + _dhcpUdpSocket.endPacket(); +} + +/** + * @brief + * @note + * @param + * @retval + */ +uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId) { + uint8_t type = 0; + uint8_t opt_len = 0; + + unsigned long startTime = clock_time(); + + while(_dhcpUdpSocket.parsePacket() <= 0) { + if((clock_time() - startTime) > responseTimeout) { + return 255; + } + + wait(0.050); + } + + // start reading in the packet + RIP_MSG_FIXED fixedMsg; + _dhcpUdpSocket.read((uint8_t*) &fixedMsg, sizeof(RIP_MSG_FIXED)); + + if(fixedMsg.op == DHCP_BOOTREPLY && _dhcpUdpSocket.remotePort() == DHCP_SERVER_PORT) { + transactionId = ntohl(fixedMsg.xid); + if + ( + memcmp(fixedMsg.chaddr, _dhcpMacAddr, 6) != 0 + || (transactionId < _dhcpInitialTransactionId) + || (transactionId > _dhcpTransactionId) + ) { + + // Need to read the rest of the packet here regardless + _dhcpUdpSocket.flush(); + return 0; + } + + memcpy(_dhcpLocalIp, fixedMsg.yiaddr, 4); + + // Skip to the option part + // Doing this a byte at a time so we don't have to put a big buffer + // on the stack (as we don't have lots of memory lying around) + for(int i = 0; i < (240 - (int)sizeof(RIP_MSG_FIXED)); i++) { + _dhcpUdpSocket.read(); // we don't care about the returned byte + } + + while(_dhcpUdpSocket.available() > 0) { + switch(_dhcpUdpSocket.read()) { + case endOption: + break; + + case padOption: + break; + + case dhcpMessageType: + opt_len = _dhcpUdpSocket.read(); + type = _dhcpUdpSocket.read(); + break; + + case subnetMask: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read(_dhcpSubnetMask, 4); + break; + + case routersOnSubnet: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read(_dhcpGatewayIp, 4); + for(int i = 0; i < opt_len - 4; i++) { + _dhcpUdpSocket.read(); + } + break; + + case dns: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read(_dhcpDnsServerIp, 4); + for(int i = 0; i < opt_len - 4; i++) { + _dhcpUdpSocket.read(); + } + break; + + case dhcpServerIdentifier: + opt_len = _dhcpUdpSocket.read(); + if(*((uint32_t*)_dhcpDhcpServerIp) == 0 || IPAddress(_dhcpDhcpServerIp) == _dhcpUdpSocket.remoteIP()) { + _dhcpUdpSocket.read(_dhcpDhcpServerIp, sizeof(_dhcpDhcpServerIp)); + } + else { + + // Skip over the rest of this option + while(opt_len--) { + _dhcpUdpSocket.read(); + } + } + break; + + case dhcpT1value: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read((uint8_t*) &_dhcpT1, sizeof(_dhcpT1)); + _dhcpT1 = ntohl(_dhcpT1); + break; + + case dhcpT2value: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read((uint8_t*) &_dhcpT2, sizeof(_dhcpT2)); + _dhcpT2 = ntohl(_dhcpT2); + break; + + case dhcpIPaddrLeaseTime: + opt_len = _dhcpUdpSocket.read(); + _dhcpUdpSocket.read((uint8_t*) &_dhcpLeaseTime, sizeof(_dhcpLeaseTime)); + _dhcpLeaseTime = ntohl(_dhcpLeaseTime); + _renewInSec = _dhcpLeaseTime; + break; + + default: + opt_len = _dhcpUdpSocket.read(); + + // Skip over the rest of this option + while(opt_len--) { + _dhcpUdpSocket.read(); + } + break; + } + } + } + + // Need to skip to end of the packet regardless here + _dhcpUdpSocket.flush(); + + return type; +} + +/* + returns: + 0/DHCP_CHECK_NONE: nothing happened + 1/DHCP_CHECK_RENEW_FAIL: renew failed + 2/DHCP_CHECK_RENEW_OK: renew success + 3/DHCP_CHECK_REBIND_FAIL: rebind fail + 4/DHCP_CHECK_REBIND_OK: rebind success +*/ +int DhcpClass::checkLease(void) { + + //this uses a signed / unsigned trick to deal with millis overflow + unsigned long now = clock_time(); + signed long snow = (long)now; + int rc = DHCP_CHECK_NONE; + if(_lastCheck != 0) { + signed long factor; + //calc how many ms past the timeout we are + + factor = snow - (long)_secTimeout; + + //if on or passed the timeout, reduce the counters + if(factor >= 0) { + + //next timeout should be now plus 1000 ms minus parts of second in factor + _secTimeout = snow + 1000 - factor % 1000; + + //how many seconds late are we, minimum 1 + factor = factor / 1000 + 1; + + //reduce the counters by that mouch + //if we can assume that the cycle time (factor) is fairly constant + //and if the remainder is less than cycle time * 2 + //do it early instead of late + if(_renewInSec < factor * 2) + _renewInSec = 0; + else + _renewInSec -= factor; + + if(_rebindInSec < factor * 2) + _rebindInSec = 0; + else + _rebindInSec -= factor; + } + + //if we have a lease but should renew, do it + if(_dhcp_state == STATE_DHCP_LEASED && _renewInSec <= 0) { + _dhcp_state = STATE_DHCP_REREQUEST; + rc = 1 + request_DHCP_lease(); + } + + //if we have a lease or is renewing but should bind, do it + if((_dhcp_state == STATE_DHCP_LEASED || _dhcp_state == STATE_DHCP_START) && _rebindInSec <= 0) { + + //this should basically restart completely + _dhcp_state = STATE_DHCP_START; + reset_DHCP_lease(); + rc = 3 + request_DHCP_lease(); + } + } + else { + _secTimeout = snow + 1000; + } + + _lastCheck = now; + return rc; +} + +/** + * @brief + * @note + * @param + * @retval + */ +IPAddress DhcpClass::getLocalIp(void) { + return IPAddress(_dhcpLocalIp); +} + +/** + * @brief + * @note + * @param + * @retval + */ +IPAddress DhcpClass::getSubnetMask(void) { + return IPAddress(_dhcpSubnetMask); +} + +/** + * @brief + * @note + * @param + * @retval + */ +IPAddress DhcpClass::getGatewayIp(void) { + return IPAddress(_dhcpGatewayIp); +} + +/** + * @brief + * @note + * @param + * @retval + */ +IPAddress DhcpClass::getDhcpServerIp(void) { + return IPAddress(_dhcpDhcpServerIp); +} + +/** + * @brief + * @note + * @param + * @retval + */ +IPAddress DhcpClass::getDnsServerIp(void) { + return IPAddress(_dhcpDnsServerIp); +} + +/** + * @brief + * @note + * @param + * @retval + */ +void DhcpClass::printByte(char* buf, uint8_t n) { + char* str = &buf[1]; + buf[0] = '0'; + do + { + unsigned long m = n; + n /= 16; + + char c = m - 16 * n; + *str-- = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); +}