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.

DnsClient.cpp

Committer:
hudakz
Date:
2019-09-07
Revision:
15:53715cc81c63
Parent:
9:a156d3de5647
Child:
16:269f652b4d0b

File content as of revision 15:53715cc81c63:

// Arduino DNS client for Enc28J60-based Ethernet shield
// (c) Copyright 2009-2010 MCQN Ltd.
// Released under Apache License, version 2.0
#include "UdpSocket.h"
#include "utility/util.h"

#include "DnsClient.h"
#include <string.h>
#include "mbed.h"

#define SOCKET_NONE 255

// Various flags and header field values for a DNS message

#define UDP_HEADER_SIZE             8
#define DNS_HEADER_SIZE             12
#define TTL_SIZE                    4
#define QUERY_FLAG                  (0)
#define RESPONSE_FLAG               (1 << 15)
#define QUERY_RESPONSE_MASK         (1 << 15)
#define OPCODE_STANDARD_QUERY       (0)
#define OPCODE_INVERSE_QUERY        (1 << 11)
#define OPCODE_STATUS_REQUEST       (2 << 11)
#define OPCODE_MASK                 (15 << 11)
#define AUTHORITATIVE_FLAG          (1 << 10)
#define TRUNCATION_FLAG             (1 << 9)
#define RECURSION_DESIRED_FLAG      (1 << 8)
#define RECURSION_AVAILABLE_FLAG    (1 << 7)
#define RESP_NO_ERROR               (0)
#define RESP_FORMAT_ERROR           (1)
#define RESP_SERVER_FAILURE         (2)
#define RESP_NAME_ERROR             (3)
#define RESP_NOT_IMPLEMENTED        (4)
#define RESP_REFUSED                (5)
#define RESP_MASK                   (15)
#define TYPE_A                      (0x0001)
#define CLASS_IN                    (0x0001)
#define LABEL_COMPRESSION_MASK      (0xC0)

// Port number that DNS servers listen on

#define DNS_PORT    53

// Possible return codes from ProcessResponse

#define SUCCESS             1
#define TIMED_OUT           - 1
#define INVALID_SERVER      - 2
#define TRUNCATED           - 3
#define INVALID_RESPONSE    - 4

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void DnsClient::begin(const IpAddress& aDNSServer)
{
    iDNSServer = aDNSServer;
    iRequestId = 0;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int DnsClient::inet_aton(const char* aIPAddrString, IpAddress& aResult)
{
    // See if we've been given a valid IP address
    const char*     p = aIPAddrString;
    while (*p && ((*p == '.') || ((*p >= '0') && (*p <= '9')))) {
        p++;
    }

    if (*p == '\0') {
        // It's looking promising, we haven't found any invalid characters
        p = aIPAddrString;

        int segment = 0;
        int segmentValue = 0;
        while (*p && (segment < 4)) {
            if (*p == '.') {
                // We've reached the end of a segment
                if (segmentValue > 255) {
                    // You can't have IP address segments that don't fit in a byte
                    return 0;
                }
                else {
                    aResult[segment] = (uint8_t) segmentValue;
                    segment++;
                    segmentValue = 0;
                }
            }
            else {
                // Next digit
                segmentValue = (segmentValue * 10) + (*p - '0');
            }

            p++;
        }

        // We've reached the end of address, but there'll still be the last
        // segment to deal with
        if ((segmentValue > 255) || (segment > 3)) {
            // You can't have IP address segments that don't fit in a byte,
            // or more than four segments
            return 0;
        }
        else {
            aResult[segment] = (uint8_t) segmentValue;
            return 1;
        }
    }
    else {
        return 0;
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int DnsClient::getHostByName(const char* aHostname, IpAddress& aResult)
{
    int ret = 0;
    Timer   timer;

    timer.start();

    // See if it's a numeric IP address
    if (inet_aton(aHostname, aResult)) {
        // It is, our work here is done
        return 1;
    }

    // Check we've got a valid DNS server to use
    if (iDNSServer == INADDR_NONE) {
        return INVALID_SERVER;
    }

    // Find a socket to use
    if (iUdp.begin(1024 + ((timer.read_ms() / 1000) & 0xF)) == 1) {
        // Try up to three times
        int retries = 0;
        //        while ((retries < 3) && (ret <= 0))
        {
            // Send DNS request
            ret = iUdp.beginPacket(iDNSServer, DNS_PORT);
            if (ret != 0) {
                // Now output the request data
                ret = buildRequest(aHostname);
                if (ret != 0) {
                    // And finally send the request
                    ret = iUdp.endPacket();
                    if (ret != 0) {
                        // Now wait for a response
                        int wait_retries = 0;
                        ret = TIMED_OUT;
                        while ((wait_retries < 3) && (ret == TIMED_OUT)) {
                            ret = processResponse(5000, aResult);
                            wait_retries++;
                        }
                    }
                }
            }

            retries++;
        }

        // We're done with the socket now
        iUdp.stop();
    }

    return ret;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint16_t DnsClient::buildRequest(const char* aName)
{
    // Build header
    //                                    1  1  1  1  1  1
    //      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |                      ID                       |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |                    QDCOUNT                    |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |                    ANCOUNT                    |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |                    NSCOUNT                    |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    //    |                    ARCOUNT                    |
    //    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    // As we only support one request at a time at present, we can simplify
    // some of this header
    srand(time(NULL) + 2);
    iRequestId = rand() % 0xFFFF + 1;   // generate a random ID
    uint16_t    twoByteBuffer;

    // FIXME We should also check that there's enough space available to write to, rather
    // FIXME than assume there's enough space (as the code does at present)
    iUdp.write((uint8_t*) &iRequestId, sizeof(iRequestId));

    twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG);
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    twoByteBuffer = htons(1);           // One question record
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    twoByteBuffer = 0;                  // Zero answer records
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    // and zero additional records
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    // Build question
    const char*     start = aName;
    const char*     end = start;
    uint8_t         len;
    // Run through the name being requested
    while (*end) {
        // Find out how long this section of the name is
        end = start;
        while (*end && (*end != '.')) {
            end++;
        }

        if (end - start > 0) {
            // Write out the size of this section
            len = end - start;
            iUdp.write(&len, sizeof(len));

            // And then write out the section
            iUdp.write((uint8_t*)start, end - start);
        }

        start = end + 1;
    }

    // We've got to the end of the question name, so
    // terminate it with a zero-length section
    len = 0;
    iUdp.write(&len, sizeof(len));

    // Finally the type and class of question
    twoByteBuffer = htons(TYPE_A);
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    twoByteBuffer = htons(CLASS_IN);    // Internet class of question
    iUdp.write((uint8_t*) &twoByteBuffer, sizeof(twoByteBuffer));

    // Success!  Everything buffered okay
    return 1;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int16_t DnsClient::processResponse(uint16_t aTimeout, IpAddress& aAddress)
{
    time_t  startTime = time(NULL);
    Timer   timer;

    timer.start();

    // Wait for a response packet
    while (iUdp.parsePacket() <= 0) {
        if (timer.read() > aTimeout)
            return TIMED_OUT;
        wait_ms(50);
    }

    // We've had a reply!
    // Read the UDP header
    uint8_t header[DNS_HEADER_SIZE];    // Enough space to reuse for the DNS header

    // Check that it's a response from the right server and the right port
    if ((iDNSServer != iUdp.remoteIP()) || (iUdp.remotePort() != DNS_PORT)) {
        // It's not from who we expected
        return INVALID_SERVER;
    }

    // Read through the rest of the response
    if (iUdp.available() < DNS_HEADER_SIZE) {
        return TRUNCATED;
    }

    iUdp.read(header, DNS_HEADER_SIZE);

    uint16_t    header_flags = htons(*((uint16_t*) &header[2]));
    // Check that it's a response to this request
    if
    (
        (iRequestId != (*((uint16_t*) &header[0]))) ||
        ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t) RESPONSE_FLAG)
    ) {
        // Mark the entire packet as read
        iUdp.flush();
        return INVALID_RESPONSE;
    }

    // Check for any errors in the response (or in our request)
    // although we don't do anything to get round these
    if ((header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK)) {
        // Mark the entire packet as read
        iUdp.flush();
        return -5;                      //INVALID_RESPONSE;
    }

    // And make sure we've got (at least) one answer
    uint16_t    answerCount = htons(*((uint16_t*) &header[6]));
    if (answerCount == 0) {
        // Mark the entire packet as read
        iUdp.flush();
        return -6;                      //INVALID_RESPONSE;
    }

    // Skip over any questions
    for (uint16_t i = 0; i < htons(*((uint16_t*) &header[4])); i++) {
        // Skip over the name
        uint8_t len;
        do {
            iUdp.read(&len, sizeof(len));
            if (len > 0) {
                // Don't need to actually read the data out for the string, just
                // advance ptr to beyond it
                while (len--) {
                    iUdp.read();        // we don't care about the returned byte
                }
            }
        } while (len != 0);

        // Now jump over the type and class
        for (int i = 0; i < 4; i++) {
            iUdp.read();                // we don't care about the returned byte
        }
    }

    // Now we're up to the bit we're interested in, the answer
    // There might be more than one answer (although we'll just use the first
    // type A answer) and some authority and additional resource records but
    // we're going to ignore all of them.
    for (uint16_t i = 0; i < answerCount; i++) {
        // Skip the name
        uint8_t len;
        do {
            iUdp.read(&len, sizeof(len));
            if ((len & LABEL_COMPRESSION_MASK) == 0) {
                // It's just a normal label
                if (len > 0) {
                    // And it's got a length
                    // Don't need to actually read the data out for the string,
                    // just advance ptr to beyond it
                    while (len--) {
                        iUdp.read();    // we don't care about the returned byte
                    }
                }
            }
            else {
                // This is a pointer to a somewhere else in the message for the
                // rest of the name.  We don't care about the name, and RFC1035
                // says that a name is either a sequence of labels ended with a
                // 0 length octet or a pointer or a sequence of labels ending in
                // a pointer.  Either way, when we get here we're at the end of
                // the name
                // Skip over the pointer
                iUdp.read();            // we don't care about the returned byte
                // And set len so that we drop out of the name loop
                len = 0;
            }
        } while (len != 0);

        // Check the type and class
        uint16_t    answerType;
        uint16_t    answerClass;
        iUdp.read((uint8_t*) &answerType, sizeof(answerType));
        iUdp.read((uint8_t*) &answerClass, sizeof(answerClass));

        // Ignore the Time-To-Live as we don't do any caching
        for (int i = 0; i < TTL_SIZE; i++) {
            iUdp.read();                // we don't care about the returned byte
        }

        // And read out the length of this answer
        // Don't need header_flags anymore, so we can reuse it here
        iUdp.read((uint8_t*) &header_flags, sizeof(header_flags));

        if ((htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN)) {
            if (htons(header_flags) != 4) {
                // It's a weird size
                // Mark the entire packet as read
                iUdp.flush();
                return -9;              //INVALID_RESPONSE;
            }

            iUdp.read(aAddress.rawAddress(), 4);
            return SUCCESS;
        }
        else {
            // This isn't an answer type we're after, move onto the next one
            for (uint16_t i = 0; i < htons(header_flags); i++) {
                iUdp.read();            // we don't care about the returned byte
            }
        }
    }

    // Mark the entire packet as read
    iUdp.flush();

    // If we get here then we haven't found an answer
    return -10;                         //INVALID_RESPONSE;
}