Fork for fixes
DnsClient.cpp
- Committer:
- ivo_n
- Date:
- 2020-09-26
- Revision:
- 22:a0b1d0e6d237
- Parent:
- 16:269f652b4d0b
- Child:
- 20:8d5738a6646e
File content as of revision 22:a0b1d0e6d237:
// 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"
#include "mbed_version.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;
#if MBED_MAJOR_VERSION == 2
wait_ms(50);
#else
thread_sleep_for(50);
#endif
}
// 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;
}