You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
12 KiB
C++
405 lines
12 KiB
C++
// Arduino DNS client for WizNet5100-based Ethernet shield
|
|
// (c) Copyright 2009-2010 MCQN Ltd.
|
|
// Released under Apache License, version 2.0
|
|
|
|
#include "utility/w5100.h"
|
|
#include "EthernetUdp.h"
|
|
#include "utility/util.h"
|
|
|
|
#include "Dns.h"
|
|
#include <string.h>
|
|
//#include <stdlib.h>
|
|
#include "Arduino.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
|
|
|
|
void DNSClient::begin(const IPAddress& aDNSServer)
|
|
{
|
|
iDNSServer = aDNSServer;
|
|
iRequestId = 0;
|
|
}
|
|
|
|
|
|
int DNSClient::inet_aton(const char* address, IPAddress& result)
|
|
{
|
|
uint16_t acc = 0; // Accumulator
|
|
uint8_t dots = 0;
|
|
|
|
while (*address)
|
|
{
|
|
char c = *address++;
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
acc = acc * 10 + (c - '0');
|
|
if (acc > 255) {
|
|
// Value out of [0..255] range
|
|
return 0;
|
|
}
|
|
}
|
|
else if (c == '.')
|
|
{
|
|
if (dots == 3) {
|
|
// Too much dots (there must be 3 dots)
|
|
return 0;
|
|
}
|
|
result[dots++] = acc;
|
|
acc = 0;
|
|
}
|
|
else
|
|
{
|
|
// Invalid char
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (dots != 3) {
|
|
// Too few dots (there must be 3 dots)
|
|
return 0;
|
|
}
|
|
result[3] = acc;
|
|
return 1;
|
|
}
|
|
|
|
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+(millis() & 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;
|
|
}
|
|
|
|
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 = millis(); // 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)
|
|
uint16_t _id = htons(iRequestId);
|
|
iUdp.write((uint8_t*)&_id, sizeof(_id));
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress)
|
|
{
|
|
uint32_t startTime = millis();
|
|
|
|
// Wait for a response packet
|
|
while(iUdp.parsePacket() <= 0)
|
|
{
|
|
if((millis() - startTime) > aTimeout)
|
|
return TIMED_OUT;
|
|
delay(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 = word(header[2], header[3]);
|
|
// Check that it's a response to this request
|
|
if ( (iRequestId != word(header[0], header[1])) ||
|
|
((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 = word(header[6], header[7]);
|
|
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 < word(header[4], header[5]); 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;
|
|
}
|
|
|