// Arduino DNS client for WizNet5100-based Ethernet shield // (c) Copyright 2009-2010 MCQN Ltd. // Released under Apache License, version 2.0 #include "utility/w5500.h" #include "EthernetUdp2.h" #include "utility/util.h" #include "Dns.h" #include //#include #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* 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] = (byte)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] = (byte)segmentValue; return 1; } } else { return 0; } } 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) 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; } 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 = 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; }