mbed OS5
Fork of UIPEthernet by
Diff: Dns.cpp
- Revision:
- 0:5350a66d5279
- Child:
- 2:049ce85163c5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Dns.cpp Mon Sep 15 11:12:30 2014 +0000 @@ -0,0 +1,454 @@ +// mbed DNS client for Enc28J60-based Ethernet shield +// (c) Copyright 2009-2010 MCQN Ltd. +// Released under Apache License, version 2.0 +#include "Udp.h" +#include "util.h" + +#include "Dns.h" +#include <string.h> +//#include <stdlib.h> + +#include "mbed.h" +#include "uip_clock.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; + + // 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 + (clock_time() & 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 + iRequestId = clock_time(); // 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 + */ +uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress) { + uint32_t startTime = clock_time(); + + // Wait for a response packet + + while(iUdp.parsePacket() <= 0) { + if((clock_time() - startTime) > aTimeout) + return TIMED_OUT; + wait(0.050); + } + + // 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.raw_address(), 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; +} +