/*********************************************************************
*
* Domain Name System (DNS) Client
* Module for Microchip TCP/IP Stack
* -Provides hostname to IP address translation
* -Reference: RFC 1035
*
*********************************************************************
* FileName: DNS.c
* Dependencies: UDP, ARP, Tick
* Processor: PIC18, PIC24F, PIC24H, dsPIC30F, dsPIC33F, PIC32
* Compiler: Microchip C32 v1.05 or higher
* Microchip C30 v3.12 or higher
* Microchip C18 v3.30 or higher
* HI-TECH PICC-18 PRO 9.63PL2 or higher
* Company: Microchip Technology, Inc.
*
* Software License Agreement
*
* Copyright (C) 2002-2009 Microchip Technology Inc. All rights
* reserved.
*
* Microchip licenses to you the right to use, modify, copy, and
* distribute:
* (i) the Software when embedded on a Microchip microcontroller or
* digital signal controller product ("Device") which is
* integrated into Licensee's product; or
* (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
* ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
* used in conjunction with a Microchip ethernet controller for
* the sole purpose of interfacing with the ethernet controller.
*
* You should refer to the license agreement accompanying this
* Software for additional information regarding your rights and
* obligations.
*
* THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
* WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
* LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
* PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
* BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
* THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
* SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
* (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
*
*
* Author Date Comment
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Howard Schlunder 7/31/06 Original
* Howard Schlunder 10/09/06 Added DNSBeginUsage(), DNSEndUsage()
* module ownership semaphore
* Howard Schlunder 08/28/09 Fixed name compression parsing bug
********************************************************************/
#define __DNS_C
#include "TCPIP Stack/TCPIP.h"
#if defined(STACK_USE_DNS)
/****************************************************************************
Section:
Constants and Global Variables
***************************************************************************/
#define DNS_PORT 53u // Default port for DNS resolutions
#define DNS_TIMEOUT (TICK_SECOND*1) // Elapsed time after which a DNS resolution is considered to have timed out
static UDP_SOCKET MySocket = INVALID_UDP_SOCKET; // UDP socket to use for DNS queries
static BYTE *DNSHostName; // Host name in RAM to look up
static ROM BYTE *DNSHostNameROM; // Host name in ROM to look up
static BYTE RecordType; // Record type being queried
static NODE_INFO ResolvedInfo; // Node information about the resolved node
// Semaphore flags for the DNS module
static union
{
BYTE Val;
struct
{
unsigned char DNSInUse : 1; // Indicates the DNS module is in use
unsigned char AddressValid : 1; // Indicates that the address resolution is valid and complete
unsigned char filler : 6;
} bits;
} Flags = {0x00};
// State machine for a DNS query
static enum
{
DNS_START = 0, // Initial state to reset client state variables
DNS_ARP_START_RESOLVE, // Send ARP resolution of DNS server or gateway MAC address
DNS_ARP_RESOLVE, // Wait for response to ARP request
DNS_OPEN_SOCKET, // Open UDP socket
DNS_QUERY, // Send DNS query to DNS server
DNS_GET_RESULT, // Wait for response from DNS server
DNS_FAIL, // ARP or DNS server not responding
DNS_DONE // DNS query is finished
} smDNS = DNS_DONE;
// Structure for the DNS header
typedef struct
{
WORD_VAL TransactionID;
WORD_VAL Flags;
WORD_VAL Questions;
WORD_VAL Answers;
WORD_VAL AuthoritativeRecords;
WORD_VAL AdditionalRecords;
} DNS_HEADER;
typedef struct
{
// Response name is first, but it is variable length and must be retrieved using the DNSDiscardName() function
WORD_VAL ResponseType;
WORD_VAL ResponseClass;
DWORD_VAL ResponseTTL;
WORD_VAL ResponseLen;
} DNS_ANSWER_HEADER;
/****************************************************************************
Section:
Function Prototypes
***************************************************************************/
static void DNSPutString(BYTE* String);
static void DNSDiscardName(void);
#if defined(__18CXX)
static void DNSPutROMString(ROM BYTE* String);
#else
// Non-ROM alias for C30/C32
#define DNSPutROMString(a) DNSPutString((BYTE*)a)
#endif
/*****************************************************************************
Function:
BOOL DNSBeginUsage(void)
Summary:
Claims access to the DNS module.
Description:
This function acts as a semaphore to obtain usage of the DNS module.
Call this function and ensure that it returns TRUE before calling any
other DNS APIs. Call DNSEndUsage when this application no longer
needs the DNS module so that other applications may make use of it.
Precondition:
Stack is initialized.
Parameters:
None
Return Values:
TRUE - No other DNS resolutions are in progress and the calling
application has sucessfully taken ownership of the DNS module
FALSE - The DNS module is currently in use. Yield to the stack and
attempt this call again later.
Remarks:
Ensure that DNSEndUsage is always called once your application has
obtained control of the DNS module. If this is not done, the stack
will hang for all future applications requiring DNS access.
***************************************************************************/
BOOL DNSBeginUsage(void)
{
if(Flags.bits.DNSInUse)
return FALSE;
Flags.bits.DNSInUse = TRUE;
return TRUE;
}
/*****************************************************************************
Function:
BOOL DNSEndUsage(void)
Summary:
Releases control of the DNS module.
Description:
This function acts as a semaphore to obtain usage of the DNS module.
Call this function when this application no longer needs the DNS
module so that other applications may make use of it.
Precondition:
DNSBeginUsage returned TRUE on a previous call.
Parameters:
None
Return Values:
TRUE - The address to the host name was successfully resolved.
FALSE - The DNS failed or the address does not exist.
Remarks:
Ensure that DNSEndUsage is always called once your application has
obtained control of the DNS module. If this is not done, the stack
will hang for all future applications requiring DNS access.
***************************************************************************/
BOOL DNSEndUsage(void)
{
if(MySocket != INVALID_UDP_SOCKET)
{
UDPClose(MySocket);
MySocket = INVALID_UDP_SOCKET;
}
smDNS = DNS_DONE;
Flags.bits.DNSInUse = FALSE;
return Flags.bits.AddressValid;
}
/*****************************************************************************
Function:
void DNSResolve(BYTE* Hostname, BYTE Type)
Summary:
Begins resolution of an address.
Description:
This function attempts to resolve a host name to an IP address. When
called, it starts the DNS state machine. Call DNSIsResolved repeatedly
to determine if the resolution is complete.
Only one DNS resoultion may be executed at a time. The Hostname must
not be modified in memory until the resolution is complete.
Precondition:
DNSBeginUsage returned TRUE on a previous call.
Parameters:
Hostname - A pointer to the null terminated string specifiying the
host for which to resolve an IP.
RecordType - DNS_TYPE_A or DNS_TYPE_MX depending on what type of
record resolution is desired.
Returns:
None
Remarks:
This function requires access to one UDP socket. If none are available,
MAX_UDP_SOCKETS may need to be increased.
***************************************************************************/
void DNSResolve(BYTE* Hostname, BYTE Type)
{
if(StringToIPAddress(Hostname, &ResolvedInfo.IPAddr))
{
Flags.bits.AddressValid = TRUE;
smDNS = DNS_DONE;
}
else
{
DNSHostName = Hostname;
DNSHostNameROM = NULL;
smDNS = DNS_START;
RecordType = Type;
Flags.bits.AddressValid = FALSE;
}
}
/*****************************************************************************
Function:
void DNSResolveROM(ROM BYTE* Hostname, BYTE Type)
Summary:
Begins resolution of an address.
Description:
This function attempts to resolve a host name to an IP address. When
called, it starts the DNS state machine. Call DNSIsResolved repeatedly
to determine if the resolution is complete.
Only one DNS resoultion may be executed at a time. The Hostname must
not be modified in memory until the resolution is complete.
Precondition:
DNSBeginUsage returned TRUE on a previous call.
Parameters:
Hostname - A pointer to the null terminated string specifiying the
host for which to resolve an IP.
RecordType - DNS_TYPE_A or DNS_TYPE_MX depending on what type of
record resolution is desired.
Returns:
None
Remarks:
This function requires access to one UDP socket. If none are available,
MAX_UDP_SOCKETS may need to be increased.
This function is aliased to DNSResolve on non-PIC18 platforms.
***************************************************************************/
#if defined(__18CXX)
void DNSResolveROM(ROM BYTE* Hostname, BYTE Type)
{
if(ROMStringToIPAddress(Hostname, &ResolvedInfo.IPAddr))
{
Flags.bits.AddressValid = TRUE;
smDNS = DNS_DONE;
}
else
{
DNSHostName = NULL;
DNSHostNameROM = Hostname;
smDNS = DNS_START;
RecordType = Type;
Flags.bits.AddressValid = FALSE;
}
}
#endif
/*****************************************************************************
Function:
BOOL DNSIsResolved(IP_ADDR* HostIP)
Summary:
Determines if the DNS resolution is complete and provides the IP.
Description:
Call this function to determine if the DNS resolution of an address has
been completed. If so, the resolved address will be provided in HostIP.
Precondition:
DNSResolve or DNSResolveROM has been called.
Parameters:
HostIP - A pointer to an IP_ADDR structure in which to store the
resolved IP address once resolution is complete.
Return Values:
TRUE - The DNS client has obtained an IP, or the DNS process
has encountered an error. HostIP will be 0.0.0.0 on error. Possible
errors include server timeout (i.e. DNS server not available), hostname
not in the DNS, or DNS server errors.
FALSE - The resolution process is still in progress.
***************************************************************************/
BOOL DNSIsResolved(IP_ADDR* HostIP)
{
static DWORD StartTime;
static WORD_VAL SentTransactionID __attribute__((persistent));
static BYTE vARPAttemptCount;
static BYTE vDNSAttemptCount;
BYTE i;
WORD_VAL w;
DNS_HEADER DNSHeader;
DNS_ANSWER_HEADER DNSAnswerHeader;
switch(smDNS)
{
case DNS_START:
vARPAttemptCount = 0;
vDNSAttemptCount = 0;
// No break;
case DNS_ARP_START_RESOLVE:
ARPResolve(&AppConfig.PrimaryDNSServer);
vARPAttemptCount++;
StartTime = TickGet();
smDNS = DNS_ARP_RESOLVE;
break;
case DNS_ARP_RESOLVE:
if(!ARPIsResolved(&AppConfig.PrimaryDNSServer, &ResolvedInfo.MACAddr))
{
if(TickGet() - StartTime > DNS_TIMEOUT)
smDNS = (vARPAttemptCount >= 3u) ? DNS_FAIL : DNS_ARP_START_RESOLVE;
break;
}
ResolvedInfo.IPAddr.Val = AppConfig.PrimaryDNSServer.Val;
smDNS = DNS_OPEN_SOCKET;
// No break: DNS_OPEN_SOCKET is the correct next state
case DNS_OPEN_SOCKET:
MySocket = UDPOpen(0, &ResolvedInfo, DNS_PORT);
if(MySocket == INVALID_UDP_SOCKET)
break;
smDNS = DNS_QUERY;
// No need to break, we can immediately start resolution
case DNS_QUERY:
if(!UDPIsPutReady(MySocket))
break;
// Put DNS query here
SentTransactionID.Val++;
UDPPut(SentTransactionID.v[1]);// User chosen transaction ID
UDPPut(SentTransactionID.v[0]);
UDPPut(0x01); // Standard query with recursion
UDPPut(0x00);
UDPPut(0x00); // 0x0001 questions
UDPPut(0x01);
UDPPut(0x00); // 0x0000 answers
UDPPut(0x00);
UDPPut(0x00); // 0x0000 name server resource records
UDPPut(0x00);
UDPPut(0x00); // 0x0000 additional records
UDPPut(0x00);
// Put hostname string to resolve
if(DNSHostName)
DNSPutString(DNSHostName);
else
DNSPutROMString(DNSHostNameROM);
UDPPut(0x00); // Type: DNS_TYPE_A A (host address) or DNS_TYPE_MX for mail exchange
UDPPut(RecordType);
UDPPut(0x00); // Class: IN (Internet)
UDPPut(0x01);
UDPFlush();
StartTime = TickGet();
smDNS = DNS_GET_RESULT;
break;
case DNS_GET_RESULT:
if(!UDPIsGetReady(MySocket))
{
if(TickGet() - StartTime > DNS_TIMEOUT)
smDNS = DNS_FAIL;
break;
}
// Retrieve the DNS header and de-big-endian it
UDPGet(&DNSHeader.TransactionID.v[1]);
UDPGet(&DNSHeader.TransactionID.v[0]);
// Throw this packet away if it isn't in response to our last query
if(DNSHeader.TransactionID.Val != SentTransactionID.Val)
{
UDPDiscard();
break;
}
UDPGet(&DNSHeader.Flags.v[1]);
UDPGet(&DNSHeader.Flags.v[0]);
UDPGet(&DNSHeader.Questions.v[1]);
UDPGet(&DNSHeader.Questions.v[0]);
UDPGet(&DNSHeader.Answers.v[1]);
UDPGet(&DNSHeader.Answers.v[0]);
UDPGet(&DNSHeader.AuthoritativeRecords.v[1]);
UDPGet(&DNSHeader.AuthoritativeRecords.v[0]);
UDPGet(&DNSHeader.AdditionalRecords.v[1]);
UDPGet(&DNSHeader.AdditionalRecords.v[0]);
// Remove all questions (queries)
while(DNSHeader.Questions.Val--)
{
DNSDiscardName();
UDPGet(&w.v[1]); // Question type
UDPGet(&w.v[0]);
UDPGet(&w.v[1]); // Question class
UDPGet(&w.v[0]);
}
// Scan through answers
while(DNSHeader.Answers.Val--)
{
DNSDiscardName(); // Throw away response name
UDPGet(&DNSAnswerHeader.ResponseType.v[1]); // Response type
UDPGet(&DNSAnswerHeader.ResponseType.v[0]);
UDPGet(&DNSAnswerHeader.ResponseClass.v[1]); // Response class
UDPGet(&DNSAnswerHeader.ResponseClass.v[0]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[3]); // Time to live
UDPGet(&DNSAnswerHeader.ResponseTTL.v[2]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[1]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[0]);
UDPGet(&DNSAnswerHeader.ResponseLen.v[1]); // Response length
UDPGet(&DNSAnswerHeader.ResponseLen.v[0]);
// Make sure that this is a 4 byte IP address, response type A or MX, class 1
// Check if this is Type A or MX
if( DNSAnswerHeader.ResponseType.Val == 0x0001u &&
DNSAnswerHeader.ResponseClass.Val == 0x0001u && // Internet class
DNSAnswerHeader.ResponseLen.Val == 0x0004u)
{
Flags.bits.AddressValid = TRUE;
UDPGet(&ResolvedInfo.IPAddr.v[0]);
UDPGet(&ResolvedInfo.IPAddr.v[1]);
UDPGet(&ResolvedInfo.IPAddr.v[2]);
UDPGet(&ResolvedInfo.IPAddr.v[3]);
goto DoneSearchingRecords;
}
else
{
while(DNSAnswerHeader.ResponseLen.Val--)
{
UDPGet(&i);
}
}
}
// Remove all Authoritative Records
while(DNSHeader.AuthoritativeRecords.Val--)
{
DNSDiscardName(); // Throw away response name
UDPGet(&DNSAnswerHeader.ResponseType.v[1]); // Response type
UDPGet(&DNSAnswerHeader.ResponseType.v[0]);
UDPGet(&DNSAnswerHeader.ResponseClass.v[1]); // Response class
UDPGet(&DNSAnswerHeader.ResponseClass.v[0]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[3]); // Time to live
UDPGet(&DNSAnswerHeader.ResponseTTL.v[2]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[1]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[0]);
UDPGet(&DNSAnswerHeader.ResponseLen.v[1]); // Response length
UDPGet(&DNSAnswerHeader.ResponseLen.v[0]);
// Make sure that this is a 4 byte IP address, response type A or MX, class 1
// Check if this is Type A
if( DNSAnswerHeader.ResponseType.Val == 0x0001u &&
DNSAnswerHeader.ResponseClass.Val == 0x0001u && // Internet class
DNSAnswerHeader.ResponseLen.Val == 0x0004u)
{
Flags.bits.AddressValid = TRUE;
UDPGet(&ResolvedInfo.IPAddr.v[0]);
UDPGet(&ResolvedInfo.IPAddr.v[1]);
UDPGet(&ResolvedInfo.IPAddr.v[2]);
UDPGet(&ResolvedInfo.IPAddr.v[3]);
goto DoneSearchingRecords;
}
else
{
while(DNSAnswerHeader.ResponseLen.Val--)
{
UDPGet(&i);
}
}
}
// Remove all Additional Records
while(DNSHeader.AdditionalRecords.Val--)
{
DNSDiscardName(); // Throw away response name
UDPGet(&DNSAnswerHeader.ResponseType.v[1]); // Response type
UDPGet(&DNSAnswerHeader.ResponseType.v[0]);
UDPGet(&DNSAnswerHeader.ResponseClass.v[1]); // Response class
UDPGet(&DNSAnswerHeader.ResponseClass.v[0]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[3]); // Time to live
UDPGet(&DNSAnswerHeader.ResponseTTL.v[2]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[1]);
UDPGet(&DNSAnswerHeader.ResponseTTL.v[0]);
UDPGet(&DNSAnswerHeader.ResponseLen.v[1]); // Response length
UDPGet(&DNSAnswerHeader.ResponseLen.v[0]);
// Make sure that this is a 4 byte IP address, response type A or MX, class 1
// Check if this is Type A
if( DNSAnswerHeader.ResponseType.Val == 0x0001u &&
DNSAnswerHeader.ResponseClass.Val == 0x0001u && // Internet class
DNSAnswerHeader.ResponseLen.Val == 0x0004u)
{
Flags.bits.AddressValid = TRUE;
UDPGet(&ResolvedInfo.IPAddr.v[0]);
UDPGet(&ResolvedInfo.IPAddr.v[1]);
UDPGet(&ResolvedInfo.IPAddr.v[2]);
UDPGet(&ResolvedInfo.IPAddr.v[3]);
goto DoneSearchingRecords;
}
else
{
while(DNSAnswerHeader.ResponseLen.Val--)
{
UDPGet(&i);
}
}
}
DoneSearchingRecords:
UDPDiscard();
UDPClose(MySocket);
MySocket = INVALID_UDP_SOCKET;
smDNS = DNS_DONE;
// No break, DNS_DONE is the correct step
case DNS_DONE:
// Return 0.0.0.0 if DNS resolution failed, otherwise return the
// resolved IP address
if(!Flags.bits.AddressValid)
ResolvedInfo.IPAddr.Val = 0;
HostIP->Val = ResolvedInfo.IPAddr.Val;
return TRUE;
case DNS_FAIL:
// If 3 attempts or more, quit
if(vDNSAttemptCount >= 2u)
{
// Return an invalid IP address 0.0.0.0 if we can't finish ARP or DNS query step
HostIP->Val = 0x00000000;
return TRUE;
}
vDNSAttemptCount++;
// Swap primary and secondary DNS servers if there is a secondary DNS server programmed
if(AppConfig.SecondaryDNSServer.Val)
{
AppConfig.PrimaryDNSServer.Val ^= AppConfig.SecondaryDNSServer.Val;
AppConfig.SecondaryDNSServer.Val ^= AppConfig.PrimaryDNSServer.Val;
AppConfig.PrimaryDNSServer.Val ^= AppConfig.SecondaryDNSServer.Val;
// Start another ARP resolution for the secondary server (now primary)
vARPAttemptCount = 0;
if(MySocket != INVALID_UDP_SOCKET)
{
UDPClose(MySocket);
MySocket = INVALID_UDP_SOCKET;
}
smDNS = DNS_ARP_START_RESOLVE;
}
break;
}
return FALSE;
}
/*****************************************************************************
Function:
static void DNSPutString(BYTE* String)
Summary:
Writes a string to the DNS socket.
Description:
This function writes a string to the DNS socket, ensuring that it is
properly formatted.
Precondition:
UDP socket is obtained and ready for writing.
Parameters:
String - the string to write to the UDP socket.
Returns:
None
***************************************************************************/
static void DNSPutString(BYTE* String)
{
BYTE *RightPtr;
BYTE i;
BYTE Len;
RightPtr = String;
while(1)
{
do
{
i = *RightPtr++;
} while((i != 0x00u) && (i != '.') && (i != '/') && (i != ',') && (i != '>'));
// Put the length and data
// Also, skip over the '.' in the input string
Len = (BYTE)(RightPtr-String-1);
UDPPut(Len);
String += UDPPutArray(String, Len) + 1;
if(i == 0x00u || i == '/' || i == ',' || i == '>')
break;
}
// Put the string null terminator character (zero length label)
UDPPut(0x00);
}
/*****************************************************************************
Function:
static void DNSPutROMString(ROM BYTE* String)
Summary:
Writes a ROM string to the DNS socket.
Description:
This function writes a string to the DNS socket, ensuring that it is
properly formatted.
Precondition:
UDP socket is obtained and ready for writing.
Parameters:
String - the string to write to the UDP socket.
Returns:
None
Remarks:
This function is aliased to DNSPutString on non-PIC18 platforms.
***************************************************************************/
#if defined(__18CXX)
static void DNSPutROMString(ROM BYTE* String)
{
ROM BYTE *RightPtr;
BYTE i;
BYTE Len;
RightPtr = String;
while(1)
{
do
{
i = *RightPtr++;
} while((i != 0x00u) && (i != '.') && (i != '/') && (i != ',') && (i != '>'));
// Put the length and data
// Also, skip over the '.' in the input string
Len = (BYTE)(RightPtr-String-1);
UDPPut(Len);
String += UDPPutROMArray(String, Len) + 1;
if(i == 0x00u || i == '/' || i == ',' || i == '>')
break;
}
// Put the string terminator character (zero length label)
UDPPut(0x00);
}
#endif
/*****************************************************************************
Function:
static void DNSDiscardName(void)
Summary:
Reads a name string or string pointer from the DNS socket and discards it.
Description:
This function reads a name string from the DNS socket. Each string
consists of a series of labels. Each label consists of a length prefix
byte, followed by the label bytes. At the end of the string, a zero length
label is found as termination. If name compression is used, this function
will automatically detect the pointer and discard it.
Precondition:
UDP socket is obtained and ready for reading a DNS name
Parameters:
None
Returns:
None
***************************************************************************/
static void DNSDiscardName(void)
{
BYTE i;
while(1)
{
// Get first byte which will tell us if this is a 16-bit pointer or the
// length of the first of a series of labels
if(!UDPGet(&i))
return;
// Check if this is a pointer, if so, get the reminaing 8 bits and return
if((i & 0xC0u) == 0xC0u)
{
UDPGet(&i);
return;
}
// Exit once we reach a zero length label
if(i == 0u)
return;
// Discard complete label
UDPGetArray(NULL, i);
}
}
#endif //#if defined(STACK_USE_DNS)
|