/* * FreeRTOS+UDP V1.0.4 * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * http://www.FreeRTOS.org * http://aws.amazon.com/freertos * * 1 tab == 4 spaces! */ /* Standard includes. */ #include /* FreeRTOS includes. */ #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "timers.h" /* FreeRTOS+UDP includes. */ #include "FreeRTOS_UDP_IP.h" #include "FreeRTOS_IP_Private.h" #include "FreeRTOS_DNS.h" #include "FreeRTOS_Sockets.h" #include "NetworkInterface.h" #include "IPTraceMacroDefaults.h" /* Exclude the entire file if DNS is not enabled. */ #if ipconfigUSE_DNS != 0 #if( ipconfigBYTE_ORDER == FREERTOS_LITTLE_ENDIAN ) #define dnsOUTGOING_FLAGS 0x0001 /* Standard query. */ #define dnsTYPE 0x0100 /* A record (host address. */ #define dnsCLASS 0x0100 /* IN */ #define dnsDNS_PORT 0x3500 #define dnsONE_QUESTION 0x0100 #define dnsRX_FLAGS_MASK 0x0f80 /* The bits of interest in the flags field of incoming DNS messages. */ #define dnsEXPECTED_RX_FLAGS 0x0080 /* Should be a response, without any errors. */ #else #define dnsDNS_PORT 0x35 #define dnsONE_QUESTION 0x01 #define dnsFLAG_QUERY_RESPONSE_BIT 0x8000 #define dnsFLAG_OPERATION_CODE_BITS 0x7800 #define dnsFLAG_TRUNCATION_BIT 0x0200 #define dnsFLAG_RESPONSE_CODE_BITS 0x000f #define dnsOUTGOING_FLAGS 0x0100 /* Standard query. */ #define dnsTYPE 0x0001 /* A record (host address. */ #define dnsCLASS 0x0001 /* IN */ #define dnsRX_FLAGS_MASK 0x800f /* The bits of interest in the flags field of incoming DNS messages. */ #define dnsEXPECTED_RX_FLAGS 0x8000 /* Should be a response, without any errors. */ #endif /* ipconfigBYTE_ORDER */ /* The maximum number of times a DNS request should be sent out if a response is not received, before giving up. */ #define dnsMAX_REQUEST_ATTEMPTS 5 /* If the top two bits in the first character of a name field are set then the name field is an offset to the string, rather than the string itself. */ #define dnsNAME_IS_OFFSET ( ( uint8_t ) 0xc0 ) /* * Create a socket and bind it to the standard DNS port number. Return the * the created socket - or NULL if the socket could not be created or bound. */ static xSocket_t prvCreateDNSSocket( void ); /* * Create the DNS message in the zero copy buffer passed in the first parameter. */ static size_t prvCreateDNSMessage( uint8_t *pucUDPPayloadBuffer, const char *pcHostName, uint16_t usIdentifier ); /* * Simple routine that jumps over the NAME field of a resource record. */ static uint8_t *prvSkipNameField( uint8_t *pucByte ); /* * Process a response packet from a DNS server. */ static uint32_t prvParseDNSReply( uint8_t *pucUDPPayloadBuffer, uint16_t usIdentifier ); /*-----------------------------------------------------------*/ #include "pack_struct_start.h" struct xDNSMessage { uint16_t usIdentifier; uint16_t usFlags; uint16_t usQuestions; uint16_t usAnswers; uint16_t usAuthorityRRs; uint16_t usAdditionalRRs; } #include "pack_struct_end.h" typedef struct xDNSMessage xDNSMessage_t; /*-----------------------------------------------------------*/ uint32_t FreeRTOS_gethostbyname( const char *pcHostName ) { static uint16_t usIdentifier = 0; struct freertos_sockaddr xAddress; static xSocket_t xDNSSocket = NULL; uint32_t ulIPAddress = 0UL; uint8_t *pucUDPPayloadBuffer; static uint32_t ulAddressLength; BaseType_t xAttempt; int32_t lBytes; size_t xPayloadLength; const size_t xExpectedPayloadLength = sizeof( xDNSMessage_t ) + strlen( pcHostName ) + sizeof( uint16_t ) + sizeof( uint16_t ) + 2; /* Two for the count of characters in the first subdomain part, and the string end byte */ if( xDNSSocket == NULL ) { xDNSSocket = prvCreateDNSSocket(); } if( xDNSSocket != NULL ) { /* Generate a unique identifier for this query. */ usIdentifier++; for( xAttempt = 0; xAttempt < dnsMAX_REQUEST_ATTEMPTS; xAttempt++ ) { /* Get a buffer. This uses a maximum delay, but the delay will be capped to ipconfigMAX_SEND_BLOCK_TIME_TICKS so the return value still needs to be tested. */ pucUDPPayloadBuffer = ( uint8_t * ) FreeRTOS_GetUDPPayloadBuffer( xExpectedPayloadLength, portMAX_DELAY ); if( pucUDPPayloadBuffer != NULL ) { /* Create the message in the obtained buffer. */ xPayloadLength = prvCreateDNSMessage( pucUDPPayloadBuffer, pcHostName, usIdentifier ); iptraceSENDING_DNS_REQUEST(); /* Obtain the DNS server address. */ FreeRTOS_GetAddressConfiguration( NULL, NULL, NULL, &ulIPAddress ); /* Send the DNS message. */ xAddress.sin_addr = ulIPAddress; xAddress.sin_port = dnsDNS_PORT; ulIPAddress = 0; if( FreeRTOS_sendto( xDNSSocket, pucUDPPayloadBuffer, xPayloadLength, FREERTOS_ZERO_COPY, &xAddress, sizeof( xAddress ) ) != 0 ) { /* Wait for the reply. */ lBytes = FreeRTOS_recvfrom( xDNSSocket, &pucUDPPayloadBuffer, 0, FREERTOS_ZERO_COPY, &xAddress, &ulAddressLength ); if( lBytes > 0 ) { /* The reply was received. Process it. */ ulIPAddress = prvParseDNSReply( pucUDPPayloadBuffer, usIdentifier ); /* Finished with the buffer. The zero copy interface is being used, so the buffer must be freed by the task. */ FreeRTOS_ReleaseUDPPayloadBuffer( ( void * ) pucUDPPayloadBuffer ); if( ulIPAddress != 0 ) { /* All done. */ break; } } } else { /* The message was not sent so the stack will not be releasing the zero copy - it must be released here. */ FreeRTOS_ReleaseUDPPayloadBuffer( ( void * ) pucUDPPayloadBuffer ); } } } } return ulIPAddress; } /*-----------------------------------------------------------*/ static size_t prvCreateDNSMessage( uint8_t *pucUDPPayloadBuffer, const char *pcHostName, uint16_t usIdentifier ) { xDNSMessage_t *pxDNSMessageHeader; uint8_t *pucStart, *pucByte; const uint16_t usARecordType = dnsTYPE, usClass = dnsCLASS; static const xDNSMessage_t xDefaultPartDNSHeader = { 0, /* The identifier will be overwritten. */ dnsOUTGOING_FLAGS, /* Flags set for standard query. */ dnsONE_QUESTION, /* One question is being asked. */ 0, /* No replies are included. */ 0, /* No authorities. */ 0 /* No additional authorities. */ }; /* Copy in the const part of the header. */ memcpy( ( void * ) pucUDPPayloadBuffer, ( void * ) &xDefaultPartDNSHeader, sizeof( xDefaultPartDNSHeader ) ); /* Write in a unique identifier. */ pxDNSMessageHeader = ( xDNSMessage_t * ) pucUDPPayloadBuffer; pxDNSMessageHeader->usIdentifier = usIdentifier; /* Create the resource record at the end of the header. First find the end of the header. */ pucStart = pucUDPPayloadBuffer + sizeof( xDefaultPartDNSHeader ); /* Leave a gap for the first length bytes. */ pucByte = pucStart + 1; /* Copy in the host name. */ strcpy( ( char * ) pucByte, pcHostName ); /* Mark the end of the string. */ pucByte += strlen( pcHostName ); *pucByte = 0x00; /* Walk the string to replace the '.' characters with byte counts. pucStart holds the address of the byte count. Walking the string starts after the byte count position. */ pucByte = pucStart; do { pucByte++; while( ( *pucByte != 0x00 ) && ( *pucByte != '.' ) ) { pucByte++; } /* Fill in the byte count, then move the pucStart pointer up to the found byte position. */ *pucStart = ( uint8_t ) ( ( uint32_t ) pucByte - ( uint32_t ) pucStart ); ( *pucStart )--; pucStart = pucByte; } while( *pucByte != 0x00 ); /* Finish off the record. */ pucByte++; memcpy( ( void * ) pucByte, &usARecordType, sizeof( uint16_t ) ); pucByte += sizeof( uint16_t ); memcpy( ( void * ) pucByte, &usClass, sizeof( uint16_t ) ); pucByte += sizeof( uint16_t ); /* Return the total size of the generated message, which is the space from the last written byte to the beginning of the buffer. */ return ( ( uint32_t ) pucByte - ( uint32_t ) pucUDPPayloadBuffer ); } /*-----------------------------------------------------------*/ static uint8_t *prvSkipNameField( uint8_t *pucByte ) { /* Determine if the name is the fully coded name, or an offset to the name elsewhere in the message. */ if( ( *pucByte & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) { /* Jump over the two byte offset. */ pucByte += sizeof( uint16_t ); } else { /* pucByte points to the full name. Walk over the string. */ while( *pucByte != 0x00 ) { /* The number of bytes to jump for each name section is stored in the byte before the name section. */ pucByte += ( *pucByte + 1 ); } pucByte++; } return pucByte; } /*-----------------------------------------------------------*/ static uint32_t prvParseDNSReply( uint8_t *pucUDPPayloadBuffer, uint16_t usIdentifier ) { xDNSMessage_t *pxDNSMessageHeader; uint32_t ulIPAddress = 0UL; uint8_t *pucByte; uint16_t x, usDataLength; const uint16_t usARecordType = dnsTYPE; pxDNSMessageHeader = ( xDNSMessage_t * ) pucUDPPayloadBuffer; if( pxDNSMessageHeader->usIdentifier == usIdentifier ) { if( ( pxDNSMessageHeader->usFlags & dnsRX_FLAGS_MASK ) == dnsEXPECTED_RX_FLAGS ) { /* Start at the first byte after the header. */ pucByte = pucUDPPayloadBuffer + sizeof( xDNSMessage_t ); /* Skip any question records. */ pxDNSMessageHeader->usQuestions = FreeRTOS_ntohs( pxDNSMessageHeader->usQuestions ); for( x = 0; x < pxDNSMessageHeader->usQuestions; x++ ) { /* Skip the variable length name field. */ pucByte = prvSkipNameField( pucByte ); /* Skip the type and class fields. */ pucByte += sizeof( uint32_t ); } /* Search through the answers records. */ pxDNSMessageHeader->usAnswers = FreeRTOS_ntohs( pxDNSMessageHeader->usAnswers ); for( x = 0; x < pxDNSMessageHeader->usAnswers; x++ ) { pucByte = prvSkipNameField( pucByte ); /* Is the type field that of an A record? */ if( memcmp( ( void * ) pucByte, ( void * ) &usARecordType, sizeof( uint16_t ) ) == 0 ) { /* This is the required record. Skip the type, class, and time to live fields, plus the first byte of the data length. */ pucByte += ( sizeof( uint32_t ) + sizeof( uint32_t ) + sizeof( uint8_t ) ); /* Sanity check the data length. */ if( *pucByte == sizeof( uint32_t ) ) { /* Skip the second byte of the length. */ pucByte++; /* Copy the IP address out of the record. */ memcpy( ( void * ) &ulIPAddress, ( void * ) pucByte, sizeof( uint32_t ) ); } break; } else { /* Skip the type, class and time to live fields. */ pucByte += ( sizeof( uint32_t ) + sizeof( uint32_t ) ); /* Determine the length of the data in the field. */ memcpy( ( void * ) &usDataLength, ( void * ) pucByte, sizeof( uint16_t ) ); usDataLength = FreeRTOS_ntohs( usDataLength ); /* Jump over the data lenth bytes, and the data itself. */ pucByte += usDataLength + sizeof( uint16_t ); } } } } return ulIPAddress; } /*-----------------------------------------------------------*/ static xSocket_t prvCreateDNSSocket( void ) { static xSocket_t xSocket = NULL; struct freertos_sockaddr xAddress; BaseType_t xReturn; TickType_t xTimeoutTime = 200 / portTICK_RATE_MS; /* This must be the first time this function has been called. Create the socket. */ xSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP ); /* Auto bind the port. */ xAddress.sin_port = 0; xReturn = FreeRTOS_bind( xSocket, &xAddress, sizeof( xAddress ) ); /* Check the bind was successful, and clean up if not. */ if( xReturn != 0 ) { FreeRTOS_closesocket( xSocket ); xSocket = NULL; } else { /* Set the send and receive timeouts. */ FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, ( void * ) &xTimeoutTime, sizeof( TickType_t ) ); FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_SNDTIMEO, ( void * ) &xTimeoutTime, sizeof( TickType_t ) ); } return xSocket; } #endif /* ipconfigUSE_DNS != 0 */