123
DnsClient.cpp
- Committer:
- advxolltm
- Date:
- 2022-06-06
- Revision:
- 19:543c2d21e510
- Parent:
- 18:8d5738a6646e
File content as of revision 19:543c2d21e510:
// 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(time_t aTimeout, IpAddress& aAddress) { time_t startTime = time(NULL); Timer timer; timer.start(); // Wait for a response packet while (iUdp.parsePacket() <= 0) { if ((time_t)timer.read_ms() > aTimeout) return TIMED_OUT; #if MBED_MAJOR_VERSION == 2 wait_ms(50); #else wait_us(50000); #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; }