Mbed library for ENC28J60 Ethernet modules. Full support for TCP/IP and UDP Server, Client and HTTP server (webserver). DHCP and DNS is included.

Dependents:   mBuino_ENC28_MQTT Nucleo_Web_ENC28J60 Nucleo_Web_ENC28J60_ADC Serial_over_Ethernet ... more

Library for ENC28J60 Ethernet modules.

/media/uploads/hudakz/enc28j60_module01.jpg

Ported to mbed from Norbert Truchsess's UIPEthernet library for Arduino. Thank you Norbert!

  • Full support for persistent (streaming) TCP/IP and UDP connections Client and Server each, ARP, ICMP, DHCP and DNS.
  • Works with both Mbed OS 2 and Mbed OS 5.

Usage:

  • Import the library into your project.
  • Add #include "UipEthernet.h" to main.cpp
  • Create one instance of the UipEthernet class initialized with the MAC address you'd like to use and SPI pins of the connected Mbed board.

Example programs:

Import programWebSwitch_ENC28J60

HTTP Server serving a simple webpage which enables to remotely turn a digital output on/off. Compile, download, run and type 'IP_address/secret/' (don't forget the last '/') into your web browser and hit ENTER.

Import programHTTPServer_Echo_ENC28J60

A simple HTTP server echoing received requests. Ethernet connection is over an ENC28J60 board. Usage: Type the server's IP address into you web browser and hit <ENTER>.

Import programTcpServer_ENC28J60

Simple TCP/IP Server using the UIPEthernet library for ENC28J60 Ethernet boards.

Import programTcpClient_ENC28J60

Simple TCP/IP Client using the UIPEthernet library for ENC28J60 Ethernet boards.

Import programUdpServer_ENC28J60

Simple UDP Server using the UIPEthernet library for ENC28J60 Ethernet boards.

Import programUdpClient_ENC28J60

Simple UDP Client using the UIPEthernet library for ENC28J60 Ethernet boards.

Import programMQTT_Hello_ENC28J60

MQTT Client example program. Ethernet connection is via an ENC28J60 module.

DhcpClient.cpp

Committer:
hudakz
Date:
2020-07-23
Revision:
18:8d5738a6646e
Parent:
17:1123c3fe86ca

File content as of revision 18:8d5738a6646e:

// DHCP Library v0.3 - April 25, 2009
// Author: Jordan Terrell - blog.jordanterrell.com
#include <string.h>
#include <stdlib.h>
#include "UipEthernet.h"
#include "DhcpClient.h"
#include "utility/util.h"

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int DhcpClient::begin(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);
    resetDhcpLease();

    memcpy((void*)_dhcpMacAddr, (void*)mac, 6);
    _dhcp_state = STATE_DHCP_START;
    return requestDhcpLease();
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void DhcpClient::resetDhcpLease()
{
    // zero out _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp
    memset(_dhcpLocalIp, 0, 5 * 4);
}

//return:0 on error, 1 if request is sent and response is received
int DhcpClient::requestDhcpLease()
{
#ifdef UIPETHERNET_DEBUG_UDP
    printf("requestDhcpLease(): begin\r\n");
#endif
    uint8_t messageType = 0;
    Timer           timer;

    timer.reset();
    timer.start();

    // Pick an initial transaction ID
    srand(time(NULL)+ 1);
    _dhcpTransactionId = (rand() % 2000UL) + 1;
    _dhcpInitialTransactionId = _dhcpTransactionId;

    _dhcpUdpSocket.stop();
    if (_dhcpUdpSocket.begin(DHCP_CLIENT_PORT) == 0) {
        // Couldn't get a socket
#ifdef UIPETHERNET_DEBUG_UDP
        printf("requestDhcpLease(): Couldn't get a socket\r\n");
#endif
        return 0;
    }

    presendDhcp();

    volatile int    result = 0;

    while (_dhcp_state != STATE_DHCP_LEASED) {
        if (_dhcp_state == STATE_DHCP_START) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("_dhcp_state: STATE_DHCP_START\r\n");
#endif
            _dhcpTransactionId++;

            sendDhcpMessage(DHCP_DISCOVER, (timer.read_ms() / 1000 + 1));
            _dhcp_state = STATE_DHCP_DISCOVER;
        }
        else
        if (_dhcp_state == STATE_DHCP_REREQUEST) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("_dhcp_state: STATE_DHCP_REREQUEST\r\n");
#endif
            _dhcpTransactionId++;
            sendDhcpMessage(DHCP_REQUEST, (timer.read_ms() / 1000 + 1));
            _dhcp_state = STATE_DHCP_REQUEST;
        }
        else
        if (_dhcp_state == STATE_DHCP_DISCOVER) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("_dhcp_state: STATE_DHCP_DISCOVER\r\n");
#endif
            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;
                sendDhcpMessage(DHCP_REQUEST, (timer.read_ms() / 1000 + 1));
                _dhcp_state = STATE_DHCP_REQUEST;
            }
        }
        else
        if (_dhcp_state == STATE_DHCP_REQUEST) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("_dhcp_state: STATE_DHCP_REQUEST\r\n");
#endif
            uint32_t    respId;
            messageType = parseDhcpResponse(_responseTimeout, respId);
            if (messageType == DHCP_ACK) {
#ifdef UIPETHERNET_DEBUG_UDP
                printf("messageType: DHCP_ACK\r\n");
#endif
                _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) {
 #ifdef UIPETHERNET_DEBUG_UDP
                printf("messageType: DHCP_NAK\r\n");
#endif
                _dhcp_state = STATE_DHCP_START;
            }
        }

        if (messageType == 255) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("DHCP_NAK: 255\r\n");
#endif
            messageType = 0;
            _dhcp_state = STATE_DHCP_START;
        }

        if ((result != 1) && ((timer.read_ms() / 1000) > _timeout)) {
#ifdef UIPETHERNET_DEBUG_UDP
            printf("requestDhcpLease(): timeout\r\n");
#endif
            break;
        }
    }

    // We're done with the socket now
    _dhcpUdpSocket.stop();
    _dhcpTransactionId++;

#ifdef UIPETHERNET_DEBUG_UDP
    printf("requestDhcpLease(): %d\r\n", result);
#endif
    return result;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void DhcpClient::presendDhcp()
{ }

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void DhcpClient::sendDhcpMessage(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 ENC28J60 transmit buffer
    _dhcpUdpSocket.write(buffer, 28);

    memset(buffer, 0, 32);                      // clear local buffer
    memcpy(buffer, _dhcpMacAddr, 6);            // chaddr
    //put data in ENC28J60 transmit buffer
    _dhcpUdpSocket.write(buffer, 16);

    memset(buffer, 0, 32);                      // clear local buffer
    // leave zeroed out for sname && file
    // put in ENC28J60 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 ENC28J60 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[8] = _dhcpLocalIp[0];
        buffer[9] = _dhcpDhcpServerIp[1];
        buffer[10] = _dhcpDhcpServerIp[2];
        buffer[11] = _dhcpDhcpServerIp[3];

        //put data in ENC28J60 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 ENC28J60 transmit buffer
    _dhcpUdpSocket.write(buffer, 9);

    _dhcpUdpSocket.endPacket();
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint8_t DhcpClient::parseDhcpResponse(time_t responseTimeout, uint32_t& transactionId)
{
    volatile uint8_t    type = 0;
    uint8_t             opt_len = 0;
    Timer               timer;

    timer.start();

    while (_dhcpUdpSocket.parsePacket() <= 0) {
        if ((time_t)timer.read_ms() > responseTimeout) {
            return 255;
        }
#if MBED_MAJOR_VERSION == 2
        wait_ms(50);
#else
        thread_sleep_for(50);
#endif
    }

    // 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 DhcpClient::checkLease()
{
    time_t          now = time(NULL);
    volatile int    rc = DHCP_CHECK_NONE;
    if (_lastCheck != 0) {
        //calc how many ms past the timeout we are
        time_t  factor = now - _secTimeout;

        //if on or passed the timeout, reduce the counters
        if (factor >= 0) {
            //next timeout should be now plus 1s
            _secTimeout = now + 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 + requestDhcpLease();
        }

        //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;
            resetDhcpLease();
            rc = 3 + requestDhcpLease();
        }
    }
    else {
        _secTimeout = now + 1;
    }

    _lastCheck = now;
    return rc;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
IpAddress DhcpClient::getLocalIp()
{
    return IpAddress(_dhcpLocalIp);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
IpAddress DhcpClient::getSubnetMask()
{
    return IpAddress(_dhcpSubnetMask);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
IpAddress DhcpClient::getGatewayIp()
{
    return IpAddress(_dhcpGatewayIp);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
IpAddress DhcpClient::getDhcpServerIp()
{
    return IpAddress(_dhcpDhcpServerIp);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
IpAddress DhcpClient::getDnsServerIp()
{
    return IpAddress(_dhcpDnsServerIp);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void DhcpClient::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);
}