From 03452388e294635c08318d42ab3d1eece67e178b Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Wed, 11 Mar 2026 11:53:51 +0100 Subject: [PATCH 01/10] Make mDNS/LLMNR responses generic This adds support for DNS-SD --- source/FreeRTOS_DNS_Parser.c | 652 ++++++++++++------ source/include/FreeRTOS_DNS_Globals.h | 67 +- test/build-combination/Common/main.c | 21 +- .../FreeRTOS_DNS_Parser_utest.c | 9 +- 4 files changed, 542 insertions(+), 207 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 90a7f85179..d990fc3bd9 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -44,6 +44,7 @@ #include "NetworkBufferManagement.h" #include +#include #if ( ipconfigUSE_DNS != 0 ) @@ -235,8 +236,151 @@ return uxIndex; } + + #if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) + +/** + * @brief Compare a DNS label sequence with a dot-separated name string. + * For example compare: + * "\x3www\x6google\x3com" with "www.google.com" + * + * The Dns sequence may contain pointers for label compression. + * pcDnsString MUST be within the DNS message + * + * @returns True when there is a match. False in malformed and non-matching cases. + */ + static BaseType_t DNS_NameEqual( uint8_t const * pcDnsString, + uint8_t const * pcDNSMessage, + char const * pcDotString ) + { + uint8_t const * pcDnsSegment = pcDnsString; + char const * pcDotSegment = pcDotString; + UBaseType_t uxNumPointerFollows = 5; + + configASSERT( pcDnsString >= pcDNSMessage ); + + for( ; ; ) + { + UBaseType_t const uxSegmentLength = ( UBaseType_t ) ( *pcDnsSegment ); + int ulComparison; + + if( uxSegmentLength == 0 ) + { + /* If we have reached the end of the DNS string, we should also be at the end of */ + /* the dotted string. */ + return *pcDotSegment == '\0'; + } + + if( ( uxSegmentLength & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) + { + uint16_t usLocation; + + if( uxNumPointerFollows == 0 ) + { + /* We have followed too many pointers, the message is probably malformed. */ + return pdFALSE; + } + + /* This is a pointer to another location in the DNS message. + * Follow the pointer and compare the rest of the string there. */ + usLocation = ( pcDnsSegment[ 0 ] & ~dnsNAME_IS_OFFSET ) << 8; + usLocation |= ( uint16_t ) pcDnsSegment[ 1 ]; + + if( usLocation >= ( pcDnsSegment - pcDNSMessage ) ) + { + /* The location should be before the current segment, otherwise we would have an infinite loop. */ + return pdFALSE; + } + + pcDnsSegment = pcDNSMessage + usLocation; + uxNumPointerFollows--; + continue; + } + + if( uxSegmentLength > 63 ) + { + /* Each segment in the DNS string must be less than 64 characters. */ + return pdFALSE; + } + + pcDnsSegment++; + /* The dot string should have a segment of the same length at this point. */ + ulComparison = strncasecmp( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); + + if( ulComparison != 0 ) + { + return pdFALSE; + } + + pcDnsSegment += uxSegmentLength; + pcDotSegment += uxSegmentLength; + + if( ( *pcDotSegment != '.' ) && ( *pcDotSegment != '\0' ) ) + { + /* Each segment in the dotted string must end in either a dot or the end of the string */ + return pdFALSE; + } + + if( *pcDotSegment == '.' ) + { + /* Move past the dot in the dotted string, and continue with the next segment. */ + pcDotSegment++; + } + } + } + +/** + * @brief Format a dot-separated name string into a DNS label sequence format in the given buffer. + * This will always take strlen(pcDotString) + 2 bytes + * + * @returns If the entire string was successfully written, then true is returned. + * Otherwise, if there was some error in the input string, false is returned. + */ + static BaseType_t DNS_WriteName( char * pcOutput, + char const * pcDotString ) + { + char const * pcDotSegment = pcDotString; + char * pcOutputSegment = pcOutput; + + for( ; ; ) + { + /* Find the length of the next segment, which is either terminated by a dot or the end of the string. */ + char const * pcEndOfSegment = strchr( pcDotSegment, '.' ); + BaseType_t const uxIsLastSegment = ( pcEndOfSegment == NULL ) ? pdTRUE : pdFALSE; + BaseType_t uxSegmentLength; + + if( uxIsLastSegment == pdTRUE ) + { + pcEndOfSegment = pcDotSegment + strlen( pcDotSegment ); + } + + uxSegmentLength = ( BaseType_t ) ( pcEndOfSegment - pcDotSegment ); + + if( ( uxSegmentLength > 63 ) || ( uxSegmentLength == 0 ) ) + { + /* Each segment in the DNS string must be less than 64 characters, */ + /*and we must have enough space to write the segment length and the segment itself. */ + return pdFALSE; + } + + *pcOutputSegment++ = ( char ) uxSegmentLength; + memcpy( pcOutputSegment, pcDotSegment, ( size_t ) uxSegmentLength ); + pcOutputSegment += uxSegmentLength; + pcDotSegment = pcEndOfSegment + 1; + + if( uxIsLastSegment ) + { + /* Write the zero-length segment at the end of the name. */ + *pcOutputSegment = 0; + return pdTRUE; + } + } + } + + #endif /* if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) */ + /** - * @brief Process a response packet from a DNS server, or an LLMNR reply. + * @brief Process a response packet from a DNS server, or an LLMNR/MDNS reply. * * @param[in] pucUDPPayloadBuffer The DNS response received as a UDP * payload. @@ -260,11 +404,14 @@ { ParseSet_t xSet; uint16_t x; + UBaseType_t i; BaseType_t xReturn = pdTRUE; uint32_t ulIPAddress = 0U; BaseType_t xDNSHookReturn = 0; NetworkBufferDescriptor_t * pxNewBuffer = NULL; + /* i is an iteration variable used in later code. If it's unused, no issue. */ + ( void ) i; ( void ) memset( &( xSet ), 0, sizeof( xSet ) ); xSet.usPortNumber = usPort; xSet.ppxLastAddress = &( xSet.pxLastAddress ); @@ -299,6 +446,14 @@ size_t uxBytesRead = 0U; size_t uxResult; BaseType_t xIsResponse = pdFALSE; + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + NetworkBufferDescriptor_t * pxNetworkBuffer; + struct xNetworkEndPoint * pxEndPoint; + struct xNetworkEndPoint xEndPoint; + UBaseType_t uxRecordCount; + DNSRecord_t * pxDNSRecords; + BaseType_t uxDNSRecordMatched = pdFALSE; + #endif /* Start at the first byte after the header. */ xSet.pucUDPPayloadBuffer = pucUDPPayloadBuffer; @@ -324,6 +479,11 @@ if( xSet.usQuestions == 0U ) { + /* mDNS yields unsolicited responses, i.e. answers with no questions. + * In the normal DNS case, pcRequestedName points to the name field + * of the first question. Since we have no questions, we will point it + * to the name field of the first answer, which comes straight after + * the header. */ #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) { xSet.pcRequestedName = ( char * ) xSet.pucByte; @@ -347,15 +507,52 @@ } } + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( pucUDPPayloadBuffer ); + + if( pxNetworkBuffer == NULL ) + { + FreeRTOS_printf( ( "DNS_ParseDNSReply: pucUDPPayloadBuffer was invalid\n" ) ); + break; + } + + if( pxNetworkBuffer->pxEndPoint == NULL ) + { + break; + } + + pxEndPoint = pxNetworkBuffer->pxEndPoint; + + /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed + * to write into it. */ + ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); + + /* Fetch our DNS record listing and mark them all, initially, + * as "do not include". */ + #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) + pxDNSRecords = xApplicationDNSRecordQueryHook( &uxRecordCount ); + #else + pxDNSRecords = xApplicationDNSRecordQueryHook_Multi( &xEndPoint, &uxRecordCount ); + #endif + uxDNSRecordMatched = pdFALSE; + + for( i = 0; i < uxRecordCount; i++ ) + { + pxDNSRecords[ i ].uxIncludeInAnswer = pdFALSE; + } + #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ + for( x = 0U; x < xSet.usQuestions; x++ ) { #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - { + const uint8_t * const pucThisNameField = xSet.pucByte; + if( x == 0U ) { + /* We assume that any answers relate to the first question. + * So we use its name for the pcRequestedName field. */ xSet.pcRequestedName = ( char * ) xSet.pucByte; } - } #endif #if ( ( ipconfigUSE_DNS_CACHE != 0 ) || ( ipconfigDNS_USE_CALLBACKS != 0 ) || ( ipconfigUSE_MDNS != 0 ) || ( ipconfigUSE_LLMNR != 0 ) ) @@ -384,16 +581,6 @@ xSet.pucByte = &( xSet.pucByte[ uxResult ] ); xSet.uxSourceBytesRemaining -= uxResult; - if( ( x == 0U ) && ( xSet.usPortNumber == ipMDNS_PORT ) ) - { - /* Note that the Questions section turns into the Answers section. - * uxSkipCount points to the first byte after e.g. 'name.local' */ - /* MISRA Ref 10.8.1 [Misaligned access] */ - /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-108 */ - /* coverity[misra_c_2012_rule_10_8_violation] */ - xSet.uxSkipCount = ( size_t ) ( xSet.pucByte - pucUDPPayloadBuffer ); - } - /* Check the remaining buffer size. */ if( xSet.uxSourceBytesRemaining >= sizeof( uint32_t ) ) { @@ -402,6 +589,42 @@ /* usChar2u16 returns value in host endianness. */ xSet.usType = usChar2u16( xSet.pucByte ); xSet.usClass = usChar2u16( &( xSet.pucByte[ 2 ] ) ); + + /* For each DNS record we serve, check if this question matches it. */ + for( i = 0; i < uxRecordCount; i++ ) + { + DNSRecord_t * pRecord = &pxDNSRecords[ i ]; + BaseType_t xTypeMatch; + BaseType_t xNameMatch; + + switch( pRecord->usRecordType ) + { + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + #endif + #if ( ipconfigUSE_IPv6 != 0 ) + case dnsTYPE_AAAA_HOST: + #endif + case dnsTYPE_SRV: + case dnsTYPE_TXT: + case dnsTYPE_PTR: + break; + + default: + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); + /* Unsupported record type. Skip. */ + continue; + } + + xTypeMatch = ( pRecord->usRecordType == xSet.usType ) || ( xSet.usType == dnsTYPE_ANY ); + xNameMatch = DNS_NameEqual( pucThisNameField, ( const uint8_t * ) xSet.pxDNSMessageHeader, pRecord->pcName ) == pdTRUE; + + if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) + { + pRecord->uxIncludeInAnswer = pdTRUE; + uxDNSRecordMatched = pdTRUE; + } + } } #endif /* ipconfigUSE_LLMNR */ @@ -429,224 +652,261 @@ } #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - - /* No need to check that pcRequestedName != NULL since sQuestions != 0, then - * pcRequestedName is assigned with this statement - * "pcRequestedName = ( char * ) pucByte;" */ - /* No need to check that usQuestions != 0, since the check is done before */ - else if( ( ( xSet.usType == dnsTYPE_A_HOST ) || ( xSet.usType == dnsTYPE_AAAA_HOST ) ) && - ( xSet.usClass == dnsCLASS_IN ) ) + else if( uxDNSRecordMatched == pdTRUE ) { - NetworkBufferDescriptor_t * pxNetworkBuffer; - NetworkEndPoint_t * pxEndPoint, xEndPoint; - size_t uxUDPOffset; + UBaseType_t const uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); + UBaseType_t uxExtraSize = 0; + UBaseType_t uxDataLength; + UBaseType_t pxNumAnswers = 0; + uint8_t * pucNewBuffer = NULL; + uint8_t * start_of_dns_answers; + UBaseType_t uxIsLLMNR; + BaseType_t usLength; + configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); - #if ( ipconfigUSE_IPv6 == 0 ) - /* Don't treat AAAA request when IPv6 is not enabled. */ - if( xSet.usType == dnsTYPE_AAAA_HOST ) - { - break; - } - #endif - #if ( ipconfigUSE_IPv4 == 0 ) - /* Don't treat A request when IPv4 is not enabled. */ - if( xSet.usType == dnsTYPE_A_HOST ) + uxDataLength = uxBufferLength + + sizeof( UDPHeader_t ) + + sizeof( EthernetHeader_t ) + + uxIPHeaderSizePacket( pxNetworkBuffer ); + + /* Calculate how big our response is going to end up being. */ + for( i = 0; i < uxRecordCount; i++ ) + { + DNSRecord_t const * pRecord = &pxDNSRecords[ i ]; + + if( pRecord->uxIncludeInAnswer == pdFALSE ) { - break; + continue; } - #endif - pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( pucUDPPayloadBuffer ); + pxNumAnswers++; + uxExtraSize += strlen( pRecord->pcName ) + 2; /* Name */ + uxExtraSize += 2; /* Type */ + uxExtraSize += 2; /* Class */ + uxExtraSize += 4; /* TTL */ + uxExtraSize += 2; /* RDLENGTH */ - /* This test could be replaced with a assert(). */ - if( pxNetworkBuffer == NULL ) - { - /* _HT_ just while testing. When the program gets here, - * pucUDPPayloadBuffer was invalid. */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: pucUDPPayloadBuffer was invalid\n" ) ); - break; - } - - uxUDPOffset = ( size_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); - configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); + switch( pRecord->usRecordType ) + { + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + uxExtraSize += 4; /* IPV4 address */ - if( pxNetworkBuffer->pxEndPoint == NULL ) - { - break; + break; + #endif /* ipconfigUSE_IPv4 */ + #if ( ipconfigUSE_IPv6 != 0 ) + case dnsTYPE_AAAA_HOST: + uxExtraSize += 16; /* IPV6 address */ + + break; + #endif /* ipconfigUSE_IPv6 */ + case dnsTYPE_SRV: + uxExtraSize += 2; /* Priority */ + uxExtraSize += 2; /* Weight */ + uxExtraSize += 2; /* Port */ + uxExtraSize += strlen( pRecord->xData.xSrvRecord.pcTarget ) + 2; /* Target */ + break; + + case dnsTYPE_TXT: + /* TXT records don't use length-label strings, so it's not +2. */ + /* Just a length field and no null terminator. So it's +1. */ + uxExtraSize += strlen( pRecord->xData.pcTxtRecord ) + 1; /* Text. */ + break; + + case dnsTYPE_PTR: + uxExtraSize += strlen( pRecord->xData.pcPtrRecord ) + 2; /* Domain; */ + break; + + default: + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); + break; + } } - pxEndPoint = pxNetworkBuffer->pxEndPoint; + if( xBufferAllocFixedSize == pdFALSE ) + { + /* Set the size of the outgoing packet. */ + pxNetworkBuffer->xDataLength = uxDataLength; + pxNewBuffer = pxDuplicateNetworkBufferWithDescriptor( pxNetworkBuffer, + uxDataLength + + uxExtraSize ); - /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed - * to write into it. */ - ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); + if( pxNewBuffer != NULL ) + { + BaseType_t xOffset1, xOffset2; - #if ( ipconfigUSE_IPv6 != 0 ) - { - /*logging*/ - FreeRTOS_printf( ( "DNS_ParseDNSReply[%s]: type %04X\n", xSet.pcName, xSet.usType ) ); + xOffset1 = ( BaseType_t ) ( xSet.pucByte - pucUDPPayloadBuffer ); + xOffset2 = ( BaseType_t ) ( ( ( uint8_t * ) xSet.pcRequestedName ) - pucUDPPayloadBuffer ); - xEndPoint.usDNSType = ( uint8_t ) xSet.usType; - } - #endif /* ( ipconfigUSE_IPv6 != 0 ) */ - - /* If this is not a reply to our DNS request, it might be an mDNS or an LLMNR - * request. Ask the application if it uses the name. */ - #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) - xDNSHookReturn = xApplicationDNSQueryHook( xSet.pcName ); - #else - xDNSHookReturn = xApplicationDNSQueryHook_Multi( &xEndPoint, xSet.pcName ); - #endif + pxNetworkBuffer = pxNewBuffer; + pucNewBuffer = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); - /* During the early stages of boot or after a DHCP lease expires, our end-point - * may have an IP address of 0.0.0.0. Do not respond to name queries with that address. */ - if( ( xDNSHookReturn != pdFALSE ) && ( xEndPoint.bits.bIPv6 == pdFALSE ) && ( xEndPoint.ipv4_settings.ulIPAddress == 0U ) ) - { - xDNSHookReturn = pdFALSE; + xSet.pucByte = &( pucNewBuffer[ xOffset1 ] ); + xSet.pcRequestedName = ( char * ) &( pucNewBuffer[ xOffset2 ] ); + xSet.pxDNSMessageHeader = ( ( DNSMessage_t * ) pucNewBuffer ); + } + else + { + /* Just to indicate that the message may not be answered. */ + pxNetworkBuffer = NULL; + } } - - if( xDNSHookReturn != pdFALSE ) + else { - int16_t usLength; - LLMNRAnswer_t * pxAnswer; - uint8_t * pucNewBuffer = NULL; - /* Number of bytes to write the Answers section: 16 (A) or 28 (AAAA). */ - size_t uxExtraLength = 0U; - size_t uxDataLength = uxBufferLength + /* Length of the UDP payload buffer */ - sizeof( UDPHeader_t ) + /* Length of the UDP header */ - sizeof( EthernetHeader_t ) + /* Length of the Ethernet header */ - uxIPHeaderSizePacket( pxNetworkBuffer ); /* Lentgh of IP 20 or 40. */ - - #if ( ipconfigUSE_IPv6 != 0 ) - if( xSet.usType == dnsTYPE_AAAA_HOST ) - { - /* The number of bytes needed by Answers section (28 bytes). */ - uxExtraLength = sizeof( LLMNRAnswer_t ) + ipSIZE_OF_IPv6_ADDRESS - sizeof( pxAnswer->ulIPAddress ); - } - else - #endif /* ( ipconfigUSE_IPv6 != 0 ) */ - #if ( ipconfigUSE_IPv4 != 0 ) + /* When xBufferAllocFixedSize is TRUE, check if the buffer size is big enough. */ + if( ( uxDataLength + uxExtraSize ) <= ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER ) { - /* The number of bytes needed by Answers section (16 bytes). */ - uxExtraLength = sizeof( LLMNRAnswer_t ); + pucNewBuffer = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); } - #else /* ( ipconfigUSE_IPv4 != 0 ) */ + else { - /* do nothing, coverity happy */ + /* Just to indicate that the message may not be answered. */ + pxNetworkBuffer = NULL; } - #endif /* ( ipconfigUSE_IPv4 != 0 ) */ + } - if( xBufferAllocFixedSize == pdFALSE ) - { - /* Set the size of the outgoing packet. - * setting 'xDataLength' determines the minimum number of bytes copied. */ - pxNetworkBuffer->xDataLength = uxDataLength; - pxNewBuffer = pxDuplicateNetworkBufferWithDescriptor( pxNetworkBuffer, - uxDataLength + - uxExtraLength ); - - if( pxNewBuffer != NULL ) - { - BaseType_t xOffset1, xOffset2; + if( !pxNetworkBuffer ) + { + break; + } - xOffset1 = ( BaseType_t ) ( xSet.pucByte - pucUDPPayloadBuffer ); - xOffset2 = ( BaseType_t ) ( ( ( uint8_t * ) xSet.pcRequestedName ) - pucUDPPayloadBuffer ); + /* We leave 'usIdentifier' and 'usQuestions' untouched */ + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usFlags, dnsLLMNR_FLAGS_IS_RESPONSE ); /* Set the response flag */ + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, pxNumAnswers ); + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, 0 ); - pxNetworkBuffer = pxNewBuffer; - pucNewBuffer = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); + start_of_dns_answers = xSet.pucByte; - xSet.pucByte = &( pucNewBuffer[ xOffset1 ] ); - xSet.pcRequestedName = ( char * ) &( pucNewBuffer[ xOffset2 ] ); - xSet.pxDNSMessageHeader = ( ( DNSMessage_t * ) pucNewBuffer ); - } - else - { - /* Just to indicate that the message may not be answered. */ - pxNetworkBuffer = NULL; - } - } - else + for( i = 0; i < uxRecordCount; i++ ) + { + DNSRecord_t const * record = &pxDNSRecords[ i ]; + MDNSResponseMiddle_t * middle; + + if( record->uxIncludeInAnswer == pdFALSE ) { - /* When xBufferAllocFixedSize is TRUE, check if the buffer size is big enough to - * store the answer. */ - if( ( uxDataLength + uxExtraLength ) <= ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER ) - { - pucNewBuffer = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); - } - else - { - /* Just to indicate that the message may not be answered. */ - pxNetworkBuffer = NULL; - } + continue; } - if( ( pxNetworkBuffer != NULL ) ) + if( !DNS_WriteName( ( char * ) xSet.pucByte, record->pcName ) ) { - size_t uxDistance; - - /* We leave 'usIdentifier' and 'usQuestions' untouched */ - if( xSet.uxSkipCount >= 2 ) - { - /* Four bytes back to write usType/usClass. */ - pxAnswer = ( LLMNRAnswer_t * ) ( &( pucNewBuffer[ xSet.uxSkipCount - 2 ] ) ); - /* Follow RFC6762 to set QR bit (section 18.2) and authoritative answer (AA) bit (section 18.4). */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usFlags, dnsMDNS_FLAGS_IS_RESPONSE ); - /* When replying to an mDNS request, do not include the Questions section. */ - xSet.usQuestions = 0; - } - else - { - pxAnswer = ( ( LLMNRAnswer_t * ) xSet.pucByte ); - /* Follow RFC4795 to set QR bit (section 2.1.1) */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usFlags, dnsLLMNR_FLAGS_IS_RESPONSE ); - } - - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usQuestions, xSet.usQuestions ); /* Might be zero. */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, 1 ); /* Provide a single answer */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, 0 ); /* No additional info */ - - if( xSet.usQuestions > 0 ) - { - pxAnswer->ucNameCode = dnsNAME_IS_OFFSET; - pxAnswer->ucNameOffset = ( uint8_t ) ( xSet.pcRequestedName - ( char * ) pucNewBuffer ); - } + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write name %s\n", record->pcName ) ); + /* This should not happen, since we have already calculated the required size. */ + break; + } - vSetField16( pxAnswer, LLMNRAnswer_t, usType, xSet.usType ); /* Type A or AAAA: host */ - vSetField16( pxAnswer, LLMNRAnswer_t, usClass, dnsCLASS_IN ); /* 1: Class IN */ - vSetField32( pxAnswer, LLMNRAnswer_t, ulTTL, dnsLLMNR_TTL_VALUE ); + xSet.pucByte += strlen( record->pcName ) + 2; + middle = ( MDNSResponseMiddle_t * ) xSet.pucByte; - uxDistance = ( size_t ) ( ( ( const uint8_t * ) pxAnswer ) - pucNewBuffer ); + vSetField16( middle, MDNSResponseMiddle_t, usType, record->usRecordType ); + vSetField16( middle, MDNSResponseMiddle_t, usClass, dnsCLASS_IN ); /* 1: Class IN */ + vSetField32( middle, MDNSResponseMiddle_t, ulTTL, dnsLLMNR_TTL_VALUE ); + switch( record->usRecordType ) + { + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + { + MDNSResponseHostAEnd_t * host_end; + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, 4 ); + xSet.pucByte += sizeof( *middle ); + host_end = ( MDNSResponseHostAEnd_t * ) xSet.pucByte; + vSetField32( host_end, MDNSResponseHostAEnd_t, ipAddr, FreeRTOS_ntohl( pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress ) ); + xSet.pucByte += sizeof( *host_end ); + break; + } + #endif /* ipconfigUSE_IPv4 */ #if ( ipconfigUSE_IPv6 != 0 ) - if( xSet.usType == dnsTYPE_AAAA_HOST ) - { - vSetField16( pxAnswer, LLMNRAnswer_t, usDataLength, ipSIZE_OF_IPv6_ADDRESS ); - ( void ) memcpy( &( pxAnswer->ulIPAddress ), xEndPoint.ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); - /* An extra 12 bytes will be sent compared to an A-record. */ - usLength = ( int16_t ) ( sizeof( *pxAnswer ) + uxDistance + ipSIZE_OF_IPv6_ADDRESS - sizeof( pxAnswer->ulIPAddress ) ); - } - else - #endif /* ( ipconfigUSE_IPv6 != 0 ) */ - { - vSetField16( pxAnswer, LLMNRAnswer_t, usDataLength, ( uint16_t ) sizeof( pxAnswer->ulIPAddress ) ); - vSetField32( pxAnswer, LLMNRAnswer_t, ulIPAddress, FreeRTOS_ntohl( xEndPoint.ipv4_settings.ulIPAddress ) ); - usLength = ( int16_t ) ( sizeof( *pxAnswer ) + uxDistance ); - } - - prepareReplyDNSMessage( pxNetworkBuffer, usLength ); - - /* This function will fill in the eth addresses and send the packet. - * xReleaseAfterSend = pdFALSE. */ - vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); + case dnsTYPE_AAAA_HOST: + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, ipSIZE_OF_IPv6_ADDRESS ); + xSet.pucByte += sizeof( *middle ); + ( void ) memcpy( xSet.pucByte, pxNetworkBuffer->pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + xSet.pucByte += ipSIZE_OF_IPv6_ADDRESS; + break; + #endif /* ipconfigUSE_IPv6 */ + case dnsTYPE_PTR: + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcPtrRecord ) + 2 ); + xSet.pucByte += sizeof( *middle ); + DNS_WriteName( ( char * ) xSet.pucByte, record->xData.pcPtrRecord ); + xSet.pucByte += strlen( record->xData.pcPtrRecord ) + 2; + break; + + case dnsTYPE_SRV: + { + MDNSResponseSRVEnd_t * srv_end; + vSetField16( + middle, + MDNSResponseMiddle_t, + usDataLength, + sizeof( MDNSResponseSRVEnd_t ) + strlen( record->xData.xSrvRecord.pcTarget ) + 2 ); + xSet.pucByte += sizeof( *middle ); + srv_end = ( MDNSResponseSRVEnd_t * ) xSet.pucByte; + vSetField16( srv_end, MDNSResponseSRVEnd_t, priority, 0 ); + vSetField16( srv_end, MDNSResponseSRVEnd_t, weight, 0 ); + vSetField16( srv_end, MDNSResponseSRVEnd_t, port, record->xData.xSrvRecord.usPort ); + xSet.pucByte += sizeof( *srv_end ); + DNS_WriteName( ( char * ) xSet.pucByte, record->xData.xSrvRecord.pcTarget ); + xSet.pucByte += strlen( record->xData.xSrvRecord.pcTarget ) + 2; + break; + } + + case dnsTYPE_TXT: + { + size_t xTextLength; + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcTxtRecord ) + 1 ); + xSet.pucByte += sizeof( *middle ); + xTextLength = strlen( record->xData.pcTxtRecord ); + + if( xTextLength > 255 ) + { + /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: TXT record too long: %s\n", record->xData.pcTxtRecord ) ); + xTextLength = 0; + break; + } + + *xSet.pucByte++ = ( uint8_t ) xTextLength; + memcpy( xSet.pucByte, record->xData.pcTxtRecord, xTextLength ); + xSet.pucByte += xTextLength; + break; + } } } + + if( xSet.usPortNumber == ipLLMNR_PORT ) + { + uxIsLLMNR = pdTRUE; + } + else if( xSet.usPortNumber == ipMDNS_PORT ) + { + uxIsLLMNR = pdFALSE; + } else { - /* Not an expected reply. */ + /* Should not happen. Let's refuse to send our answer */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unexpected port number %u\n", xSet.usPortNumber ) ); + break; } + + if( uxIsLLMNR == pdFALSE ) + { + /* In MDNS, we need to remove the questions section */ + uint8_t * const start_of_questions = ( uint8_t * ) ( xSet.pxDNSMessageHeader ) + sizeof( DNSMessage_t ); + + UBaseType_t const size_of_questions = ( UBaseType_t ) ( start_of_dns_answers - start_of_questions ); + + UBaseType_t const size_of_answers = ( UBaseType_t ) ( xSet.pucByte - start_of_dns_answers ); + memmove( start_of_questions, start_of_dns_answers, size_of_answers ); + xSet.pucByte -= size_of_questions; + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usQuestions, 0 ); + } + + usLength = ( BaseType_t ) ( xSet.pucByte - pucNewBuffer ); + prepareReplyDNSMessage( pxNetworkBuffer, usLength ); + /* This function will fill in the eth addresses and send the packet */ + vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); } - #endif /* ipconfigUSE_LLMNR == 1 */ + #endif /* ipconfigUSE_LLMNR == 1 || ipconfigUSE_MDNS == 1 */ ( void ) uxBytesRead; } while( ipFALSE_BOOL ); @@ -1223,9 +1483,9 @@ xEndPoint.usDNSType = dnsTYPE_A_HOST; #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) - xDNSHookReturn = xApplicationDNSQueryHook( ( const char * ) ucNBNSName ); + xDNSHookReturn = xApplicationNBNSQueryHook( ( const char * ) ucNBNSName ); #else - xDNSHookReturn = xApplicationDNSQueryHook_Multi( &( xEndPoint ), ( const char * ) ucNBNSName ); + xDNSHookReturn = xApplicationNBNSQueryHook_Multi( &( xEndPoint ), ( const char * ) ucNBNSName ); #endif if( xDNSHookReturn == pdFALSE ) diff --git a/source/include/FreeRTOS_DNS_Globals.h b/source/include/FreeRTOS_DNS_Globals.h index 36d74b359e..365aaff2c3 100644 --- a/source/include/FreeRTOS_DNS_Globals.h +++ b/source/include/FreeRTOS_DNS_Globals.h @@ -68,8 +68,12 @@ #define dnsTYPE_A_HOST 0x01U /**< DNS type A host. */ #define dnsTYPE_AAAA_HOST 0x001CU #define dnsTYPE_ANY_HOST 0x00FFU + #define dnsTYPE_TXT 0x0010U /**< DNS type TXT (Text Record). */ + #define dnsTYPE_PTR 0x000CU /**< DNS type PTR (Pointer Record). */ + #define dnsTYPE_SRV 0x0021U /**< DNS type SRV (Service Record). */ + #define dnsTYPE_ANY 0x00FFU /**< DNS type ANY. */ - #define dnsCLASS_IN 0x01U /**< DNS class IN (Internet). */ + #define dnsCLASS_IN 0x01U /**< DNS class IN (Internet). */ /* Maximum hostname length as defined in RFC 1035 section 3.1. */ #define dnsMAX_HOSTNAME_LENGTH 0xFFU @@ -177,7 +181,6 @@ uint16_t usAnswers; /**< The number of DNS answers that were given. */ uint8_t * pucUDPPayloadBuffer; /**< A pointer to the original UDP load buffer. */ uint8_t * pucByte; /**< A pointer that is used while parsing. */ - size_t uxSkipCount; /**< Points to the byte after the complete name (mDNS only) */ size_t uxBufferLength; /**< The total number of bytes received in the UDP payload. */ size_t uxSourceBytesRemaining; /**< As pucByte is incremented, 'uxSourceBytesRemaining' will be decremented. */ uint16_t usType; /**< The type of address, recognised are dnsTYPE_A_HOST ( Ipv4 ) and @@ -216,6 +219,35 @@ } #include "pack_struct_end.h" typedef struct xLLMNRAnswer LLMNRAnswer_t; + + #include "pack_struct_start.h" + struct xMDNSResponseMiddle + { + uint16_t usType; /**< Type of the Resource record. */ + uint16_t usClass; /**< Class of the Resource record. */ + uint32_t ulTTL; /**< Seconds till this entry can be cached. */ + uint16_t usDataLength; /**< Length of the address in this record. */ + } + #include "pack_struct_end.h" + typedef struct xMDNSResponseMiddle MDNSResponseMiddle_t; + + #include "pack_struct_start.h" + struct xMDNSResponseSRVEnd + { + uint16_t priority; + uint16_t weight; + uint16_t port; + } + #include "pack_struct_end.h" + typedef struct xMDNSResponseSRVEnd MDNSResponseSRVEnd_t; + + #include "pack_struct_start.h" + struct xMDNSResponseHostAEnd + { + uint32_t ipAddr; + } + #include "pack_struct_end.h" + typedef struct xMDNSResponseHostAEnd MDNSResponseHostAEnd_t; #endif /* if ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) */ #if ( ipconfigUSE_NBNS == 1 ) @@ -285,6 +317,24 @@ #if ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) + typedef struct xDNSRecord + { + uint16_t usRecordType; + /* Used by the backend to determine which fields to report */ + BaseType_t uxIncludeInAnswer; + const char * pcName; + union + { + char * pcPtrRecord; + struct + { + const char * pcTarget; + uint16_t usPort; + } xSrvRecord; + char * pcTxtRecord; + } xData; + } DNSRecord_t; + /* * The following function should be provided by the user and return true if it * matches the domain name. @@ -292,15 +342,18 @@ #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) /* Even though the function is defined in main.c, the rule is violated. */ /* misra_c_2012_rule_8_6_violation */ - extern BaseType_t xApplicationDNSQueryHook( const char * pcName ); + extern DNSRecord_t * xApplicationDNSRecordQueryHook( UBaseType_t * outLen ); + extern BaseType_t xApplicationNBNSQueryHook( const char * pcName ); #else /* Even though the function is defined in main.c, the rule is violated. */ /* misra_c_2012_rule_8_6_violation */ - extern BaseType_t xApplicationDNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - const char * pcName ); - #endif + extern DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + UBaseType_t * outLen ); + extern BaseType_t xApplicationNBNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + const char * pcName ); + #endif /* if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) */ - #endif /* ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) */ + #endif /* ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) */ #endif /* ipconfigUSE_DNS */ /* Keeping this outside of ipconfigUSE_DNS flag as these are used inside IPv4 UDP code */ diff --git a/test/build-combination/Common/main.c b/test/build-combination/Common/main.c index f904ad5a7e..ec9db0c332 100644 --- a/test/build-combination/Common/main.c +++ b/test/build-combination/Common/main.c @@ -172,7 +172,25 @@ int main( void ) #if ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_NBNS != 0 ) - BaseType_t xApplicationDNSQueryHook( const char * pcName ) + DNSRecord_t * xApplicationDNSRecordQueryHook( UBaseType_t * outLen ) + { + static DNSRecord_t xRecord[ 2 ] = + { + { + .pcName = mainHOST_NAME, + .usRecordType = dnsTYPE_A_HOST, + }, + { + .pcName = mainDEVICE_NICK_NAME, + .usRecordType = dnsTYPE_A_HOST, + } + }; + + *outLen = 2; + return xRecord; + } + + BaseType_t xApplicationNBNSQueryHook( const char * pcName ) { BaseType_t xReturn; @@ -195,6 +213,7 @@ int main( void ) return xReturn; } + #endif /* if ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_NBNS != 0 ) */ /*-----------------------------------------------------------*/ diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index ac18be894a..4055931087 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -4002,11 +4002,14 @@ void test_parseDNSAnswer_remaining_lt_dnsanswerrecord( void ) TEST_ASSERT_EQUAL( 44, uxBytesRead ); } -BaseType_t xApplicationDNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - const char * pcName ) +DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + UBaseType_t * outLen ) { + static DNSRecord_t xRecord; + hook_called = pdTRUE; - return hook_return; + *outLen = 0; + return &xRecord; } void test_prepareReplyDNSMessage_null_pointer( void ) From a2021a0fabc991fa3b5a422c10f5c94fd169488e Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Thu, 12 Mar 2026 09:46:54 +0100 Subject: [PATCH 02/10] Fix OOB issue in DNS_NameEqual --- source/FreeRTOS_DNS_Parser.c | 68 +++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index d990fc3bd9..736c72f377 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -245,19 +245,27 @@ * "\x3www\x6google\x3com" with "www.google.com" * * The Dns sequence may contain pointers for label compression. - * pcDnsString MUST be within the DNS message + * pcDNSMessage <= pcDNSString < pcDNSMessageEnd must hold. If following labels + * would walk past pcDNSMessageEnd, an error will be returned. + * + * @param[in] pcDNSString Pointer to the DNS label string. + * @param[in] pcDNSMessage Pointer to the start of the DNS payload. + * @param[in] pcDNSMessageEnd The pointer to one-past-the-end of the DNS payload. + * @param[in] pcDotString The pointer to the dot-separated string. * * @returns True when there is a match. False in malformed and non-matching cases. */ - static BaseType_t DNS_NameEqual( uint8_t const * pcDnsString, + static BaseType_t DNS_NameEqual( uint8_t const * pcDNSString, uint8_t const * pcDNSMessage, + uint8_t const * pcDNSMessageEnd, char const * pcDotString ) { - uint8_t const * pcDnsSegment = pcDnsString; + uint8_t const * pcDnsSegment = pcDNSString; char const * pcDotSegment = pcDotString; UBaseType_t uxNumPointerFollows = 5; - configASSERT( pcDnsString >= pcDNSMessage ); + configASSERT( pcDNSString >= pcDNSMessage ); + configASSERT( pcDNSString < pcDNSMessageEnd ); for( ; ; ) { @@ -266,13 +274,15 @@ if( uxSegmentLength == 0 ) { - /* If we have reached the end of the DNS string, we should also be at the end of */ - /* the dotted string. */ + /* If we have reached the end of the DNS string, we should also be at the end of + * the dotted string. */ return *pcDotSegment == '\0'; } if( ( uxSegmentLength & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) { + /* This is a compressed label pointing to previous data in the DNS body */ + uint16_t usLocation; if( uxNumPointerFollows == 0 ) @@ -284,6 +294,13 @@ /* This is a pointer to another location in the DNS message. * Follow the pointer and compare the rest of the string there. */ usLocation = ( pcDnsSegment[ 0 ] & ~dnsNAME_IS_OFFSET ) << 8; + + if( ( pcDnsSegment + 1 ) >= pcDNSMessageEnd ) + { + /* Reading one further would go past the end of the message */ + return pdFALSE; + } + usLocation |= ( uint16_t ) pcDnsSegment[ 1 ]; if( usLocation >= ( pcDnsSegment - pcDNSMessage ) ) @@ -304,6 +321,33 @@ } pcDnsSegment++; + + if( ( pcDnsSegment + uxSegmentLength ) >= pcDNSMessageEnd ) + { + /* The segment would go past the end of the message, so the message is malformed. */ + return pdFALSE; + } + + /* Verify that the next dot-string segment is exactly uxSegmentLength bytes + * (terminated by '.' or '\0'). Without this check, an attacker-controlled + * uxSegmentLength larger than the dot-string segment combined with a + * NUL byte in the DNS data could cause strncasecmp to match prematurely, + * advancing pcDotSegment past the end of pcDotString. */ + { + UBaseType_t uxDotSegLen = 0; + + while( ( pcDotSegment[ uxDotSegLen ] != '.' ) && + ( pcDotSegment[ uxDotSegLen ] != '\0' ) ) + { + uxDotSegLen++; + } + + if( uxDotSegLen != uxSegmentLength ) + { + return pdFALSE; + } + } + /* The dot string should have a segment of the same length at this point. */ ulComparison = strncasecmp( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); @@ -315,11 +359,7 @@ pcDnsSegment += uxSegmentLength; pcDotSegment += uxSegmentLength; - if( ( *pcDotSegment != '.' ) && ( *pcDotSegment != '\0' ) ) - { - /* Each segment in the dotted string must end in either a dot or the end of the string */ - return pdFALSE; - } + /* pcDotSegment now points to '.' or '\0' (verified above). */ if( *pcDotSegment == '.' ) { @@ -617,7 +657,11 @@ } xTypeMatch = ( pRecord->usRecordType == xSet.usType ) || ( xSet.usType == dnsTYPE_ANY ); - xNameMatch = DNS_NameEqual( pucThisNameField, ( const uint8_t * ) xSet.pxDNSMessageHeader, pRecord->pcName ) == pdTRUE; + xNameMatch = DNS_NameEqual( + pucThisNameField, + ( const uint8_t * ) xSet.pxDNSMessageHeader, + xSet.pucUDPPayloadBuffer + xSet.uxBufferLength, + pRecord->pcName ) == pdTRUE; if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) { From decb55b1524806eae605908bd0db15a3b9fcf014 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Thu, 12 Mar 2026 14:37:16 +0100 Subject: [PATCH 03/10] Back compat for old style DNS hooks --- source/FreeRTOS_DNS_Parser.c | 361 +++++++++++++--------- source/include/FreeRTOSIPConfigDefaults.h | 19 ++ source/include/FreeRTOS_DNS_Globals.h | 94 +++--- 3 files changed, 292 insertions(+), 182 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 736c72f377..6281b1d7db 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -369,6 +369,7 @@ } } + /** * @brief Format a dot-separated name string into a DNS label sequence format in the given buffer. * This will always take strlen(pcDotString) + 2 bytes @@ -416,9 +417,205 @@ } } } - #endif /* if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) */ + + +/** + * @brief Process the DNS questions and populated relevant fields in xSet + * Assumes that the xSet parsing context is set to the start of the + * questions section + * + * If LLMNR/MDNS are enabled then the following will be populated: + * - pcRequestedName (the name in the first question) + * - pxDNSRecords and uxDNSRecordCount + * + * @param[in,out] xSet a set of variables that are shared among the helper functions. + * @param[in] pxEndPoint The end-point on which the DNS message was received. + * Necessary when LLMNR/MDNS are enabled, and IPv4_BACKWARD_COMPATIBLE is 0. + * Otherwise may be NULL. + * @return pdTRUE if everything went okay + */ + static BaseType_t parseDNSQuestions( ParseSet_t * xSet, + NetworkEndPoint_t const * pxEndPoint ) + { + UBaseType_t x; + size_t uxResult; + + ( void ) pxEndPoint; + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) + NetworkEndPoint_t xEndPoint; + configASSERT( pxEndPoint != NULL ); + + /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed + * to write into it. */ + ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); + #else + #endif + + #if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) + xSet->uxDNSRecordCount = 0; + #else + #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) + xSet->pxDNSRecords = xApplicationDNSRecordQueryHook( &xSet->uxDNSRecordCount ); + #else + xSet->pxDNSRecords = xApplicationDNSRecordQueryHook_Multi( &xEndPoint, &xSet->uxDNSRecordCount ); + #endif + + for( x = 0U; x < xSet->uxDNSRecordCount; x++ ) + { + xSet->pxDNSRecords[ x ].uxIncludeInAnswer = pdFALSE; + } + #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ + #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ + + for( x = 0U; x < xSet->usQuestions; x++ ) + { + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + const uint8_t * const pucThisNameField = xSet->pucByte; + + if( x == 0U ) + { + /* We assume that any answers relate to the first question. + * So we use its name for the pcRequestedName field. */ + xSet->pcRequestedName = ( char * ) xSet->pucByte; + } + #endif + + #if ( ( ipconfigUSE_DNS_CACHE != 0 ) || ( ipconfigDNS_USE_CALLBACKS != 0 ) || ( ipconfigUSE_MDNS != 0 ) || ( ipconfigUSE_LLMNR != 0 ) ) + if( x == 0U ) + { + uxResult = DNS_ReadNameField( xSet, + sizeof( xSet->pcName ) ); + ( void ) uxResult; + } + else + #endif /* ipconfigUSE_DNS_CACHE || ipconfigDNS_USE_CALLBACKS || ipconfigUSE_MDNS || ipconfigUSE_LLMNR */ + { + /* Skip the variable length pcName field. */ + uxResult = DNS_SkipNameField( xSet->pucByte, + xSet->uxSourceBytesRemaining ); + } + + /* Check for a malformed response. */ + if( uxResult == 0U ) + { + return pdFALSE; + } + + xSet->pucByte = &( xSet->pucByte[ uxResult ] ); + xSet->uxSourceBytesRemaining -= uxResult; + + /* Check the remaining buffer size. */ + if( xSet->uxSourceBytesRemaining >= sizeof( uint32_t ) ) + { + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + { + UBaseType_t i; + /* usChar2u16 returns value in host endianness. */ + xSet->usType = usChar2u16( xSet->pucByte ); + xSet->usClass = usChar2u16( &( xSet->pucByte[ 2 ] ) ); + + #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) + { + ( void ) i; + /* This should have been set in the main DNS function */ + configASSERT( xSet->pxDNSRecords != NULL ); + + if( x == 0U ) + { + #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) + BaseType_t const xIsMatched = xApplicationDNSQueryHook( xSet->pcName ); + #else + BaseType_t const xIsMatched = xApplicationDNSQueryHook_Multi( &xEndPoint, xSet->pcName ); + #endif + + if( xIsMatched == pdTRUE ) + { + xSet->xDNSRecordsMatched = pdTRUE; + #if ( ( ipconfigUSE_IPv6 != 0 ) ) + if( ( xSet->usType == dnsTYPE_AAAA_HOST ) || ( xSet->usType == dnsTYPE_ANY_HOST ) ) + { + xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = + ( DNSRecord_t ) { + .usRecordType = dnsTYPE_AAAA_HOST, + .pcName = xSet->pcName, + .uxIncludeInAnswer = pdTRUE, + }; + } + #endif + #if ( ( ipconfigUSE_IPv4 != 0 ) ) + if( ( xSet->usType == dnsTYPE_A_HOST ) || ( xSet->usType == dnsTYPE_ANY_HOST ) ) + { + xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = + ( DNSRecord_t ) { + .usRecordType = dnsTYPE_A_HOST, + .pcName = xSet->pcName, + .uxIncludeInAnswer = pdTRUE, + }; + } + #endif + } + } + } + #else /* if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) */ + /* For each DNS record we serve, check if this question matches it. */ + for( i = 0; i < xSet->uxDNSRecordCount; i++ ) + { + DNSRecord_t * pRecord = &xSet->pxDNSRecords[ i ]; + BaseType_t xTypeMatch; + BaseType_t xNameMatch; + + switch( pRecord->usRecordType ) + { + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + #endif + #if ( ipconfigUSE_IPv6 != 0 ) + case dnsTYPE_AAAA_HOST: + #endif + case dnsTYPE_SRV: + case dnsTYPE_TXT: + case dnsTYPE_PTR: + break; + + default: + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); + /* Unsupported record type. Skip. */ + continue; + } + + xTypeMatch = ( pRecord->usRecordType == xSet->usType ) || ( xSet->usType == dnsTYPE_ANY ); + xNameMatch = DNS_NameEqual( + pucThisNameField, + ( const uint8_t * ) xSet->pxDNSMessageHeader, + xSet->pucUDPPayloadBuffer + xSet->uxBufferLength, + pRecord->pcName ) == pdTRUE; + + if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) + { + pRecord->uxIncludeInAnswer = pdTRUE; + xSet->xDNSRecordsMatched = pdTRUE; + } + } + #endif /* if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) */ + } + #endif /* ipconfigUSE_LLMNR */ + + /* Skip the type and class fields. */ + xSet->pucByte = &( xSet->pucByte[ sizeof( uint32_t ) ] ); + xSet->uxSourceBytesRemaining -= sizeof( uint32_t ); + } + else + { + return pdFALSE; + } + } /* for( x = 0U; x < xSet.usQuestions; x++ ) */ + + return pdTRUE; + } + /** * @brief Process a response packet from a DNS server, or an LLMNR/MDNS reply. * @@ -443,7 +640,6 @@ uint16_t usPort ) { ParseSet_t xSet; - uint16_t x; UBaseType_t i; BaseType_t xReturn = pdTRUE; uint32_t ulIPAddress = 0U; @@ -483,16 +679,18 @@ /* Introduce a do {} while (0) to allow the use of breaks. */ do { - size_t uxBytesRead = 0U; size_t uxResult; BaseType_t xIsResponse = pdFALSE; #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) NetworkBufferDescriptor_t * pxNetworkBuffer; - struct xNetworkEndPoint * pxEndPoint; - struct xNetworkEndPoint xEndPoint; - UBaseType_t uxRecordCount; - DNSRecord_t * pxDNSRecords; - BaseType_t uxDNSRecordMatched = pdFALSE; + #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) + + /* We need to make dummy records to shim the old system to the new. + * We need up to two records, one for A and for AAAA. + * parseDNSQuestions will do this job. */ + DNSRecord_t xShimDNSRecords[ 2 ]; + xSet.pxDNSRecords = xShimDNSRecords; + #endif #endif /* Start at the first byte after the header. */ @@ -533,8 +731,8 @@ #if ( ipconfigUSE_DNS_CACHE == 1 ) || ( ipconfigDNS_USE_CALLBACKS == 1 ) uxResult = DNS_ReadNameField( &xSet, sizeof( xSet.pcName ) ); - ( void ) uxResult; #endif + ( void ) uxResult; } } else @@ -550,139 +748,17 @@ #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( pucUDPPayloadBuffer ); - if( pxNetworkBuffer == NULL ) + if( ( pxNetworkBuffer == NULL ) || ( pxNetworkBuffer->pxEndPoint == NULL ) ) { - FreeRTOS_printf( ( "DNS_ParseDNSReply: pucUDPPayloadBuffer was invalid\n" ) ); + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to get NetworkBufferDescriptor_t from UDP payload buffer.\n" ) ); break; } - if( pxNetworkBuffer->pxEndPoint == NULL ) - { - break; - } - - pxEndPoint = pxNetworkBuffer->pxEndPoint; - - /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed - * to write into it. */ - ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); - - /* Fetch our DNS record listing and mark them all, initially, - * as "do not include". */ - #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) - pxDNSRecords = xApplicationDNSRecordQueryHook( &uxRecordCount ); - #else - pxDNSRecords = xApplicationDNSRecordQueryHook_Multi( &xEndPoint, &uxRecordCount ); - #endif - uxDNSRecordMatched = pdFALSE; - - for( i = 0; i < uxRecordCount; i++ ) - { - pxDNSRecords[ i ].uxIncludeInAnswer = pdFALSE; - } + xReturn = parseDNSQuestions( &xSet, pxNetworkBuffer->pxEndPoint ); + #else + xReturn = parseDNSQuestions( &xSet, NULL ); #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ - for( x = 0U; x < xSet.usQuestions; x++ ) - { - #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - const uint8_t * const pucThisNameField = xSet.pucByte; - - if( x == 0U ) - { - /* We assume that any answers relate to the first question. - * So we use its name for the pcRequestedName field. */ - xSet.pcRequestedName = ( char * ) xSet.pucByte; - } - #endif - - #if ( ( ipconfigUSE_DNS_CACHE != 0 ) || ( ipconfigDNS_USE_CALLBACKS != 0 ) || ( ipconfigUSE_MDNS != 0 ) || ( ipconfigUSE_LLMNR != 0 ) ) - if( x == 0U ) - { - uxResult = DNS_ReadNameField( &xSet, - sizeof( xSet.pcName ) ); - ( void ) uxResult; - } - else - #endif /* ipconfigUSE_DNS_CACHE || ipconfigDNS_USE_CALLBACKS || ipconfigUSE_MDNS || ipconfigUSE_LLMNR */ - { - /* Skip the variable length pcName field. */ - uxResult = DNS_SkipNameField( xSet.pucByte, - xSet.uxSourceBytesRemaining ); - } - - /* Check for a malformed response. */ - if( uxResult == 0U ) - { - xReturn = pdFALSE; - break; - } - - uxBytesRead += uxResult; - xSet.pucByte = &( xSet.pucByte[ uxResult ] ); - xSet.uxSourceBytesRemaining -= uxResult; - - /* Check the remaining buffer size. */ - if( xSet.uxSourceBytesRemaining >= sizeof( uint32_t ) ) - { - #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - { - /* usChar2u16 returns value in host endianness. */ - xSet.usType = usChar2u16( xSet.pucByte ); - xSet.usClass = usChar2u16( &( xSet.pucByte[ 2 ] ) ); - - /* For each DNS record we serve, check if this question matches it. */ - for( i = 0; i < uxRecordCount; i++ ) - { - DNSRecord_t * pRecord = &pxDNSRecords[ i ]; - BaseType_t xTypeMatch; - BaseType_t xNameMatch; - - switch( pRecord->usRecordType ) - { - #if ( ipconfigUSE_IPv4 != 0 ) - case dnsTYPE_A_HOST: - #endif - #if ( ipconfigUSE_IPv6 != 0 ) - case dnsTYPE_AAAA_HOST: - #endif - case dnsTYPE_SRV: - case dnsTYPE_TXT: - case dnsTYPE_PTR: - break; - - default: - FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); - /* Unsupported record type. Skip. */ - continue; - } - - xTypeMatch = ( pRecord->usRecordType == xSet.usType ) || ( xSet.usType == dnsTYPE_ANY ); - xNameMatch = DNS_NameEqual( - pucThisNameField, - ( const uint8_t * ) xSet.pxDNSMessageHeader, - xSet.pucUDPPayloadBuffer + xSet.uxBufferLength, - pRecord->pcName ) == pdTRUE; - - if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) - { - pRecord->uxIncludeInAnswer = pdTRUE; - uxDNSRecordMatched = pdTRUE; - } - } - } - #endif /* ipconfigUSE_LLMNR */ - - /* Skip the type and class fields. */ - xSet.pucByte = &( xSet.pucByte[ sizeof( uint32_t ) ] ); - xSet.uxSourceBytesRemaining -= sizeof( uint32_t ); - } - else - { - xReturn = pdFALSE; - break; - } - } /* for( x = 0U; x < xSet.usQuestions; x++ ) */ - if( xReturn == pdFALSE ) { /* No need to proceed. Break out of the do-while loop. */ @@ -692,11 +768,11 @@ if( xIsResponse == pdTRUE ) { /* Search through the answer records. */ - ulIPAddress = parseDNSAnswer( &( xSet ), ppxAddressInfo, &uxBytesRead ); + ulIPAddress = parseDNSAnswer( &( xSet ), ppxAddressInfo, NULL ); } #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - else if( uxDNSRecordMatched == pdTRUE ) + else if( xSet.xDNSRecordsMatched == pdTRUE ) { UBaseType_t const uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); UBaseType_t uxExtraSize = 0; @@ -714,9 +790,9 @@ uxIPHeaderSizePacket( pxNetworkBuffer ); /* Calculate how big our response is going to end up being. */ - for( i = 0; i < uxRecordCount; i++ ) + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) { - DNSRecord_t const * pRecord = &pxDNSRecords[ i ]; + DNSRecord_t const * pRecord = &xSet.pxDNSRecords[ i ]; if( pRecord->uxIncludeInAnswer == pdFALSE ) { @@ -822,9 +898,9 @@ start_of_dns_answers = xSet.pucByte; - for( i = 0; i < uxRecordCount; i++ ) + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) { - DNSRecord_t const * record = &pxDNSRecords[ i ]; + DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; MDNSResponseMiddle_t * middle; if( record->uxIncludeInAnswer == pdFALSE ) @@ -951,7 +1027,6 @@ vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); } #endif /* ipconfigUSE_LLMNR == 1 || ipconfigUSE_MDNS == 1 */ - ( void ) uxBytesRead; } while( ipFALSE_BOOL ); /* coverity[deadcode] */ diff --git a/source/include/FreeRTOSIPConfigDefaults.h b/source/include/FreeRTOSIPConfigDefaults.h index 65682d0864..69ed5089b2 100644 --- a/source/include/FreeRTOSIPConfigDefaults.h +++ b/source/include/FreeRTOSIPConfigDefaults.h @@ -210,6 +210,25 @@ /*---------------------------------------------------------------------------*/ +/* + * ipconfigDNSQuery_BACKWARD_COMPATIBLE + * + * Type: BaseType_t ( ipconfigENABLE | ipconfigDISABLE ) + * + * Enables the APIs that are backward compatible with old-style DNS Query + * Hooks + */ + +#ifndef ipconfigDNSQuery_BACKWARD_COMPATIBLE + #define ipconfigDNSQuery_BACKWARD_COMPATIBLE ipconfigDISABLE +#endif + +#if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE != ipconfigDISABLE ) && ( ipconfigDNSQuery_BACKWARD_COMPATIBLE != ipconfigENABLE ) ) + #error Invalid ipconfigDNSQuery_BACKWARD_COMPATIBLE configuration +#endif + +/*---------------------------------------------------------------------------*/ + /* * ipconfigUSE_IPv4 * diff --git a/source/include/FreeRTOS_DNS_Globals.h b/source/include/FreeRTOS_DNS_Globals.h index 365aaff2c3..a855aab2e6 100644 --- a/source/include/FreeRTOS_DNS_Globals.h +++ b/source/include/FreeRTOS_DNS_Globals.h @@ -122,6 +122,58 @@ #define ipNBNS_PORT 137U /* NetBIOS Name Service. */ #define ipNBDGM_PORT 138U /* Datagram Service, not included. */ + #if ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) + + typedef struct xDNSRecord + { + uint16_t usRecordType; + /* Used by the backend to determine which fields to report */ + BaseType_t uxIncludeInAnswer; + const char * pcName; + union + { + char * pcPtrRecord; + struct + { + const char * pcTarget; + uint16_t usPort; + } xSrvRecord; + char * pcTxtRecord; + } xData; + } DNSRecord_t; + +/* + * The following function should be provided by the user and return true if it + * matches the domain name. + */ + #if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) + #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) + /* Even though the function is defined in main.c, the rule is violated. */ + /* misra_c_2012_rule_8_6_violation */ + extern BaseType_t xApplicationDNSQueryHook( const char * pcName ); + #define xApplicationNBNSQueryHook xApplicationDNSQueryHook + #else + /* Even though the function is defined in main.c, the rule is violated. */ + /* misra_c_2012_rule_8_6_violation */ + extern BaseType_t xApplicationDNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + const char * pcName ); + #define xApplicationNBNSQueryHook_Multi xApplicationDNSQueryHook_Multi + #endif + #else /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ + #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) + extern DNSRecord_t * xApplicationDNSRecordQueryHook( UBaseType_t * outLen ); + extern BaseType_t xApplicationNBNSQueryHook( const char * pcName ); + #else + extern DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + UBaseType_t * outLen ); + extern BaseType_t xApplicationNBNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + const char * pcName ); + #endif /* if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) */ + + #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ + + #endif /* ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) */ + /** @brief freertos_addrinfo is the equivalent of 'struct addrinfo'. */ struct freertos_addrinfo { @@ -194,6 +246,9 @@ #if ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) uint16_t usClass; /**< Only the value 'dnsCLASS_IN' is recognised, which stands for "Internet". */ char * pcRequestedName; /**< A pointer to the full name of the host being looked up. */ + DNSRecord_t * pxDNSRecords; /**< A pointer to an array of DNS records that are being served by this device. */ + UBaseType_t uxDNSRecordCount; /**< The number of records in the array pointed to by 'pxDNSRecords'. */ + BaseType_t xDNSRecordsMatched; /**< Becomes true when a question matches with one of the records in 'pxDNSRecords'. */ #endif #if ( ( ipconfigUSE_DNS_CACHE != 0 ) || ( ipconfigDNS_USE_CALLBACKS != 0 ) || ( ipconfigUSE_MDNS != 0 ) || ( ipconfigUSE_LLMNR != 0 ) ) @@ -315,45 +370,6 @@ size_t uxPayloadLength; /**< Payload size */ } DNSBuffer_t; - #if ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) - - typedef struct xDNSRecord - { - uint16_t usRecordType; - /* Used by the backend to determine which fields to report */ - BaseType_t uxIncludeInAnswer; - const char * pcName; - union - { - char * pcPtrRecord; - struct - { - const char * pcTarget; - uint16_t usPort; - } xSrvRecord; - char * pcTxtRecord; - } xData; - } DNSRecord_t; - -/* - * The following function should be provided by the user and return true if it - * matches the domain name. - */ - #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) - /* Even though the function is defined in main.c, the rule is violated. */ - /* misra_c_2012_rule_8_6_violation */ - extern DNSRecord_t * xApplicationDNSRecordQueryHook( UBaseType_t * outLen ); - extern BaseType_t xApplicationNBNSQueryHook( const char * pcName ); - #else - /* Even though the function is defined in main.c, the rule is violated. */ - /* misra_c_2012_rule_8_6_violation */ - extern DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - UBaseType_t * outLen ); - extern BaseType_t xApplicationNBNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - const char * pcName ); - #endif /* if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) */ - - #endif /* ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) */ #endif /* ipconfigUSE_DNS */ /* Keeping this outside of ipconfigUSE_DNS flag as these are used inside IPv4 UDP code */ From c719f2d11c133c0d6eda85bde97e8215f8588d48 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Thu, 12 Mar 2026 16:02:45 +0100 Subject: [PATCH 04/10] Tidying and local strncasecmp --- source/FreeRTOS_DNS_Parser.c | 124 ++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 32 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 6281b1d7db..6da4bb8165 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -44,7 +44,6 @@ #include "NetworkBufferManagement.h" #include -#include #if ( ipconfigUSE_DNS != 0 ) @@ -239,6 +238,47 @@ #if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) +/** + * @brief Local implementation of posix strncasecmp + */ + static int local_strncasecmp( const char * s1, + const char * s2, + size_t n ) + { + while( n-- != 0 ) + { + char c1 = *s1++; + char c2 = *s2++; + + if( ( c1 >= 'A' ) && ( c1 <= 'Z' ) ) + { + c1 += 'a' - 'A'; + } + + if( ( c2 >= 'A' ) && ( c2 <= 'Z' ) ) + { + c2 += 'a' - 'A'; + } + + if( c1 > c2 ) + { + return 1; + } + + if( c2 > c1 ) + { + return -1; + } + + if( c1 == '\0' ) + { + break; + } + } + + return 0; + } + /** * @brief Compare a DNS label sequence with a dot-separated name string. * For example compare: @@ -349,7 +389,7 @@ } /* The dot string should have a segment of the same length at this point. */ - ulComparison = strncasecmp( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); + ulComparison = local_strncasecmp( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); if( ulComparison != 0 ) { @@ -431,8 +471,8 @@ * - pxDNSRecords and uxDNSRecordCount * * @param[in,out] xSet a set of variables that are shared among the helper functions. - * @param[in] pxEndPoint The end-point on which the DNS message was received. - * Necessary when LLMNR/MDNS are enabled, and IPv4_BACKWARD_COMPATIBLE is 0. + * @param[in] pxEndPoint The endpoint on which the DNS message was received. + * Necessary when LLMNR/MDNS are enabled * Otherwise may be NULL. * @return pdTRUE if everything went okay */ @@ -442,17 +482,13 @@ UBaseType_t x; size_t uxResult; - ( void ) pxEndPoint; #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) - NetworkEndPoint_t xEndPoint; - configASSERT( pxEndPoint != NULL ); + NetworkEndPoint_t xEndPoint; + configASSERT( pxEndPoint != NULL ); - /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed - * to write into it. */ - ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); - #else - #endif + /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed + * to write into it. */ + ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); #if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) xSet->uxDNSRecordCount = 0; @@ -470,6 +506,8 @@ #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ + ( void ) pxEndPoint; + for( x = 0U; x < xSet->usQuestions; x++ ) { #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) @@ -517,11 +555,12 @@ xSet->usType = usChar2u16( xSet->pucByte ); xSet->usClass = usChar2u16( &( xSet->pucByte[ 2 ] ) ); + /* This should have been set by now. */ + configASSERT( xSet->pxDNSRecords != NULL ); + #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) { ( void ) i; - /* This should have been set in the main DNS function */ - configASSERT( xSet->pxDNSRecords != NULL ); if( x == 0U ) { @@ -535,7 +574,10 @@ { xSet->xDNSRecordsMatched = pdTRUE; #if ( ( ipconfigUSE_IPv6 != 0 ) ) - if( ( xSet->usType == dnsTYPE_AAAA_HOST ) || ( xSet->usType == dnsTYPE_ANY_HOST ) ) + if( + ( xEndPoint.bits.bIPv6 != pdFALSE ) && + ( ( xSet->usType == dnsTYPE_AAAA_HOST ) || + ( xSet->usType == dnsTYPE_ANY_HOST ) ) ) { xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = ( DNSRecord_t ) { @@ -544,9 +586,12 @@ .uxIncludeInAnswer = pdTRUE, }; } - #endif + #endif /* if ( ( ipconfigUSE_IPv6 != 0 ) ) */ #if ( ( ipconfigUSE_IPv4 != 0 ) ) - if( ( xSet->usType == dnsTYPE_A_HOST ) || ( xSet->usType == dnsTYPE_ANY_HOST ) ) + if( + ( xEndPoint.ipv4_settings.ulIPAddress != 0U ) && + ( ( xSet->usType == dnsTYPE_A_HOST ) || + ( xSet->usType == dnsTYPE_ANY_HOST ) ) ) { xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = ( DNSRecord_t ) { @@ -555,7 +600,7 @@ .uxIncludeInAnswer = pdTRUE, }; } - #endif + #endif /* if ( ( ipconfigUSE_IPv4 != 0 ) ) */ } } } @@ -581,7 +626,7 @@ break; default: - FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); + FreeRTOS_printf( ( "parseDNSQuestions: Unsupported record type %u\n", pRecord->usRecordType ) ); /* Unsupported record type. Skip. */ continue; } @@ -595,6 +640,18 @@ if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) { + if( ( pRecord->usRecordType == dnsTYPE_A_HOST ) && ( xEndPoint.ipv4_settings.ulIPAddress == 0U ) ) + { + FreeRTOS_printf( ( "parseDNSQuestions: No IPV4 address, skipping A record even though it matches.\n" ) ); + continue; + } + + if( ( pRecord->usRecordType == dnsTYPE_AAAA_HOST ) && ( xEndPoint.bits.bIPv6 == pdFALSE ) ) + { + FreeRTOS_printf( ( "parseDNSQuestions: No IPV6 address, skipping AAAA record even though it matches.\n" ) ); + continue; + } + pRecord->uxIncludeInAnswer = pdTRUE; xSet->xDNSRecordsMatched = pdTRUE; } @@ -777,11 +834,11 @@ UBaseType_t const uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); UBaseType_t uxExtraSize = 0; UBaseType_t uxDataLength; - UBaseType_t pxNumAnswers = 0; + UBaseType_t uxNumAnswers = 0; uint8_t * pucNewBuffer = NULL; uint8_t * start_of_dns_answers; UBaseType_t uxIsLLMNR; - BaseType_t usLength; + BaseType_t uxLength; configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); uxDataLength = uxBufferLength + @@ -799,7 +856,7 @@ continue; } - pxNumAnswers++; + uxNumAnswers++; uxExtraSize += strlen( pRecord->pcName ) + 2; /* Name */ uxExtraSize += 2; /* Type */ uxExtraSize += 2; /* Class */ @@ -891,9 +948,13 @@ } /* We leave 'usIdentifier' and 'usQuestions' untouched */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usFlags, dnsLLMNR_FLAGS_IS_RESPONSE ); /* Set the response flag */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, pxNumAnswers ); - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ + vSetField16( + xSet.pxDNSMessageHeader, + DNSMessage_t, + usFlags, + xSet.usPortNumber == ipLLMNR_PORT ? dnsLLMNR_FLAGS_IS_RESPONSE : dnsMDNS_FLAGS_IS_RESPONSE ); /* Set the response flag */ + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, uxNumAnswers ); + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, 0 ); start_of_dns_answers = xSet.pucByte; @@ -972,10 +1033,7 @@ case dnsTYPE_TXT: { - size_t xTextLength; - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcTxtRecord ) + 1 ); - xSet.pucByte += sizeof( *middle ); - xTextLength = strlen( record->xData.pcTxtRecord ); + size_t xTextLength = strlen( record->xData.pcTxtRecord ); if( xTextLength > 255 ) { @@ -985,6 +1043,8 @@ break; } + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcTxtRecord ) + 1 ); + xSet.pucByte += sizeof( *middle ); *xSet.pucByte++ = ( uint8_t ) xTextLength; memcpy( xSet.pucByte, record->xData.pcTxtRecord, xTextLength ); xSet.pucByte += xTextLength; @@ -1021,8 +1081,8 @@ vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usQuestions, 0 ); } - usLength = ( BaseType_t ) ( xSet.pucByte - pucNewBuffer ); - prepareReplyDNSMessage( pxNetworkBuffer, usLength ); + uxLength = ( BaseType_t ) ( xSet.pucByte - pucNewBuffer ); + prepareReplyDNSMessage( pxNetworkBuffer, uxLength ); /* This function will fill in the eth addresses and send the packet */ vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); } From b030cbe9dac8c97e863302df939a68664a50e1a8 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Fri, 13 Mar 2026 14:00:56 +0100 Subject: [PATCH 05/10] Unit tests passing --- source/FreeRTOS_DNS_Parser.c | 172 +++++++++---- source/include/FreeRTOS_DNS_Globals.h | 43 ++-- .../FreeRTOS_DNS_Parser_utest.c | 235 +++++++++--------- 3 files changed, 258 insertions(+), 192 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 6da4bb8165..334b18ca14 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -457,8 +457,85 @@ } } } - #endif /* if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) */ +/** + * @brief Get the network buffer associated with the given parse set. + * Will calculate it if necessary, otherwise just return the previously calculated value. + * + * @param[in,out] xSet A set of variables that are shared among the helper functions. + * @return A pointer to the network buffer descriptor. + */ + static NetworkBufferDescriptor_t * DNS_GetNetworkBuffer( ParseSet_t * xSet ) + { + if( xSet->pxNetworkBuffer == NULL ) + { + xSet->pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( xSet->pucUDPPayloadBuffer ); + } + + return xSet->pxNetworkBuffer; + } + +/** + * @brief Get the endpoint (by copy) associated with the given parse set. + * + * @param[in,out] xSet A set of variables that are shared among the helper functions. + * @param[out] pxEndPointOut A pointer to the endpoint structure where the result will be stored. + * @return pdTRUE if the endpoint was successfully retrieved, pdFALSE otherwise. + */ + static BaseType_t DNS_GetEndpoint( ParseSet_t * xSet, + NetworkEndPoint_t * pxEndPointOut ) + { + NetworkBufferDescriptor_t * pxNetworkBuffer = DNS_GetNetworkBuffer( xSet ); + + if( ( pxNetworkBuffer == NULL ) || ( pxNetworkBuffer->pxEndPoint == NULL ) ) + { + return pdFALSE; + } + + memcpy( pxEndPointOut, pxNetworkBuffer->pxEndPoint, sizeof( NetworkEndPoint_t ) ); + return pdTRUE; + } + +/** + * @brief Get the DNS records to be used for answering the questions in xSet. + * Will call the application hook if necessary, or return a cached answer if not. + * @param[in,out] xSet A set of variables that are shared among the helper functions. + * @return pdTRUE if the records were successfully retrieved, pdFALSE otherwise. + */ + static BaseType_t DNS_GetRecords( ParseSet_t * xSet ) + { + UBaseType_t x; + + #if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) + + /* No need to do anything. We aren't *really* fetching records in the back-compatible + * path, we're instead creating them on the way*/ + #else + NetworkEndPoint_t xEndPoint; + + if( xSet->pxDNSRecords == NULL ) + { + #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) + ( void ) xEndPoint; + xSet->pxDNSRecords = xApplicationDNSRecordQueryHook( &xSet->uxDNSRecordCount ); + #else + if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) + { + return pdFALSE; + } + xSet->pxDNSRecords = xApplicationDNSRecordQueryHook_Multi( &xEndPoint, &xSet->uxDNSRecordCount ); + #endif + + for( x = 0U; x < xSet->uxDNSRecordCount; x++ ) + { + xSet->pxDNSRecords[ x ].uxIncludeInAnswer = pdFALSE; + } + } + #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ + return pdTRUE; + } + + #endif /* if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) */ /** @@ -471,47 +548,23 @@ * - pxDNSRecords and uxDNSRecordCount * * @param[in,out] xSet a set of variables that are shared among the helper functions. - * @param[in] pxEndPoint The endpoint on which the DNS message was received. - * Necessary when LLMNR/MDNS are enabled - * Otherwise may be NULL. * @return pdTRUE if everything went okay */ - static BaseType_t parseDNSQuestions( ParseSet_t * xSet, - NetworkEndPoint_t const * pxEndPoint ) + static BaseType_t parseDNSQuestions( ParseSet_t * xSet ) { UBaseType_t x; size_t uxResult; #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) NetworkEndPoint_t xEndPoint; - configASSERT( pxEndPoint != NULL ); - - /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed - * to write into it. */ - ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); - - #if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) - xSet->uxDNSRecordCount = 0; - #else - #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) - xSet->pxDNSRecords = xApplicationDNSRecordQueryHook( &xSet->uxDNSRecordCount ); - #else - xSet->pxDNSRecords = xApplicationDNSRecordQueryHook_Multi( &xEndPoint, &xSet->uxDNSRecordCount ); - #endif - - for( x = 0U; x < xSet->uxDNSRecordCount; x++ ) - { - xSet->pxDNSRecords[ x ].uxIncludeInAnswer = pdFALSE; - } - #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ - #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ - - ( void ) pxEndPoint; + const uint8_t * pucThisNameField; + BaseType_t xEndPointValid = pdFALSE; + #endif for( x = 0U; x < xSet->usQuestions; x++ ) { #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - const uint8_t * const pucThisNameField = xSet->pucByte; + pucThisNameField = xSet->pucByte; if( x == 0U ) { @@ -555,8 +608,10 @@ xSet->usType = usChar2u16( xSet->pucByte ); xSet->usClass = usChar2u16( &( xSet->pucByte[ 2 ] ) ); - /* This should have been set by now. */ - configASSERT( xSet->pxDNSRecords != NULL ); + if( DNS_GetRecords( xSet ) == pdFALSE ) + { + return pdFALSE; + } #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) { @@ -564,10 +619,22 @@ if( x == 0U ) { + BaseType_t xIsMatched; + + if( xEndPointValid == pdFALSE ) + { + if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) + { + return pdFALSE; + } + + xEndPointValid = pdTRUE; + } + #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) - BaseType_t const xIsMatched = xApplicationDNSQueryHook( xSet->pcName ); + xIsMatched = xApplicationDNSQueryHook( xSet->pcName ); #else - BaseType_t const xIsMatched = xApplicationDNSQueryHook_Multi( &xEndPoint, xSet->pcName ); + xIsMatched = xApplicationDNSQueryHook_Multi( &xEndPoint, xSet->pcName ); #endif if( xIsMatched == pdTRUE ) @@ -640,7 +707,17 @@ if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) { - if( ( pRecord->usRecordType == dnsTYPE_A_HOST ) && ( xEndPoint.ipv4_settings.ulIPAddress == 0U ) ) + if( xEndPointValid == pdFALSE ) + { + if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) + { + return pdFALSE; + } + + xEndPointValid = pdTRUE; + } + + if( ( pRecord->usRecordType == dnsTYPE_A_HOST ) && ( ENDPOINT_IS_IPv4( &xEndPoint ) && ( xEndPoint.ipv4_settings.ulIPAddress == 0U ) ) ) { FreeRTOS_printf( ( "parseDNSQuestions: No IPV4 address, skipping A record even though it matches.\n" ) ); continue; @@ -658,7 +735,7 @@ } #endif /* if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) */ } - #endif /* ipconfigUSE_LLMNR */ + #endif /* ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ /* Skip the type and class fields. */ xSet->pucByte = &( xSet->pucByte[ sizeof( uint32_t ) ] ); @@ -739,7 +816,6 @@ size_t uxResult; BaseType_t xIsResponse = pdFALSE; #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - NetworkBufferDescriptor_t * pxNetworkBuffer; #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) /* We need to make dummy records to shim the old system to the new. @@ -747,6 +823,7 @@ * parseDNSQuestions will do this job. */ DNSRecord_t xShimDNSRecords[ 2 ]; xSet.pxDNSRecords = xShimDNSRecords; + xSet.uxDNSRecordCount = 0; #endif #endif @@ -802,19 +879,7 @@ } } - #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( pucUDPPayloadBuffer ); - - if( ( pxNetworkBuffer == NULL ) || ( pxNetworkBuffer->pxEndPoint == NULL ) ) - { - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to get NetworkBufferDescriptor_t from UDP payload buffer.\n" ) ); - break; - } - - xReturn = parseDNSQuestions( &xSet, pxNetworkBuffer->pxEndPoint ); - #else - xReturn = parseDNSQuestions( &xSet, NULL ); - #endif /* if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ + xReturn = parseDNSQuestions( &xSet ); if( xReturn == pdFALSE ) { @@ -831,7 +896,6 @@ #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) else if( xSet.xDNSRecordsMatched == pdTRUE ) { - UBaseType_t const uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); UBaseType_t uxExtraSize = 0; UBaseType_t uxDataLength; UBaseType_t uxNumAnswers = 0; @@ -839,6 +903,12 @@ uint8_t * start_of_dns_answers; UBaseType_t uxIsLLMNR; BaseType_t uxLength; + UBaseType_t uxUDPOffset; + NetworkBufferDescriptor_t * pxNetworkBuffer; + pxNetworkBuffer = DNS_GetNetworkBuffer( &xSet ); + /* This test could be replaced with a assert(). */ + configASSERT( pxNetworkBuffer != NULL ); + uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); uxDataLength = uxBufferLength + diff --git a/source/include/FreeRTOS_DNS_Globals.h b/source/include/FreeRTOS_DNS_Globals.h index a855aab2e6..3ed8e7fc88 100644 --- a/source/include/FreeRTOS_DNS_Globals.h +++ b/source/include/FreeRTOS_DNS_Globals.h @@ -33,8 +33,10 @@ #include "FreeRTOSIPConfig.h" #include "FreeRTOSIPConfigDefaults.h" +#include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" + #define dnsPARSE_ERROR 0UL #if ( ipconfigBYTE_ORDER == pdFREERTOS_LITTLE_ENDIAN ) @@ -228,27 +230,28 @@ */ typedef struct xParseSet { - DNSMessage_t * pxDNSMessageHeader; /**< A pointer to the UDP payload buffer where the DNS message is stored. */ - uint16_t usQuestions; /**< The number of DNS questions that were asked. */ - uint16_t usAnswers; /**< The number of DNS answers that were given. */ - uint8_t * pucUDPPayloadBuffer; /**< A pointer to the original UDP load buffer. */ - uint8_t * pucByte; /**< A pointer that is used while parsing. */ - size_t uxBufferLength; /**< The total number of bytes received in the UDP payload. */ - size_t uxSourceBytesRemaining; /**< As pucByte is incremented, 'uxSourceBytesRemaining' will be decremented. */ - uint16_t usType; /**< The type of address, recognised are dnsTYPE_A_HOST ( Ipv4 ) and - * dnsTYPE_AAAA_HOST ( IPv6 ). */ - uint32_t ulIPAddress; /**< The IPv4 address found. In an IPv6 look-up, store a non-zero value when - * an IPv6 address was found. */ - size_t uxAddressLength; /**< The size of the address, either ipSIZE_OF_IPv4_ADDRESS or - * ipSIZE_OF_IPv6_ADDRESS */ - uint16_t usNumARecordsStored; /**< The number of A-records stored during a look-up. */ - uint16_t usPortNumber; /**< The port number that belong to the protocol ( DNS, MDNS etc ). */ + DNSMessage_t * pxDNSMessageHeader; /**< A pointer to the UDP payload buffer where the DNS message is stored. */ + uint16_t usQuestions; /**< The number of DNS questions that were asked. */ + uint16_t usAnswers; /**< The number of DNS answers that were given. */ + uint8_t * pucUDPPayloadBuffer; /**< A pointer to the original UDP load buffer. */ + uint8_t * pucByte; /**< A pointer that is used while parsing. */ + size_t uxBufferLength; /**< The total number of bytes received in the UDP payload. */ + size_t uxSourceBytesRemaining; /**< As pucByte is incremented, 'uxSourceBytesRemaining' will be decremented. */ + uint16_t usType; /**< The type of address, recognised are dnsTYPE_A_HOST ( Ipv4 ) and + * dnsTYPE_AAAA_HOST ( IPv6 ). */ + uint32_t ulIPAddress; /**< The IPv4 address found. In an IPv6 look-up, store a non-zero value when + * an IPv6 address was found. */ + size_t uxAddressLength; /**< The size of the address, either ipSIZE_OF_IPv4_ADDRESS or + * ipSIZE_OF_IPv6_ADDRESS */ + uint16_t usNumARecordsStored; /**< The number of A-records stored during a look-up. */ + uint16_t usPortNumber; /**< The port number that belong to the protocol ( DNS, MDNS etc ). */ #if ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) - uint16_t usClass; /**< Only the value 'dnsCLASS_IN' is recognised, which stands for "Internet". */ - char * pcRequestedName; /**< A pointer to the full name of the host being looked up. */ - DNSRecord_t * pxDNSRecords; /**< A pointer to an array of DNS records that are being served by this device. */ - UBaseType_t uxDNSRecordCount; /**< The number of records in the array pointed to by 'pxDNSRecords'. */ - BaseType_t xDNSRecordsMatched; /**< Becomes true when a question matches with one of the records in 'pxDNSRecords'. */ + uint16_t usClass; /**< Only the value 'dnsCLASS_IN' is recognised, which stands for "Internet". */ + char * pcRequestedName; /**< A pointer to the full name of the host being looked up. */ + DNSRecord_t * pxDNSRecords; /**< A pointer to an array of DNS records that are being served by this device. */ + UBaseType_t uxDNSRecordCount; /**< The number of records in the array pointed to by 'pxDNSRecords'. */ + BaseType_t xDNSRecordsMatched; /**< Becomes true when a question matches with one of the records in 'pxDNSRecords'. */ + NetworkBufferDescriptor_t * pxNetworkBuffer; /**< A network buffer that is used to prepare an LLMNR or MDNS response. */ #endif #if ( ( ipconfigUSE_DNS_CACHE != 0 ) || ( ipconfigDNS_USE_CALLBACKS != 0 ) || ( ipconfigUSE_MDNS != 0 ) || ( ipconfigUSE_LLMNR != 0 ) ) diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index 4055931087..0f9bd214e9 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -70,7 +70,9 @@ static int callback_called = 0; static BaseType_t hook_return = pdFALSE; static BaseType_t hook_called = pdFALSE; - +static char prvDNSRecordNames[ 2 ][ 256 ]; +static DNSRecord_t prvDNSRecords[ 2 ]; +static UBaseType_t prvDNSRecordsLen = 0; /* =========================== STATIC FUNCTIONS =========================== */ static void dns_callback( const char * pcName, @@ -96,6 +98,7 @@ void setUp( void ) { xBufferAllocFixedSize = pdFALSE; callback_called = 0; + prvDNSRecordsLen = 0; } /** @@ -118,6 +121,33 @@ void tearDown( void ) TEST_ASSERT_EQUAL( pdFALSE, hook_called ); \ } while( 0 ) + +/* ============================= DNS Records =============================== */ + +DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + UBaseType_t * outLen ) +{ + hook_called = pdTRUE; + *outLen = prvDNSRecordsLen; + return prvDNSRecords; +} + +static void SetDNSRecords( char const * pcName ) +{ + strncpy( prvDNSRecordNames[ 0 ], pcName, sizeof( prvDNSRecordNames[ 0 ] ) - 1 ); + strncpy( prvDNSRecordNames[ 1 ], pcName, sizeof( prvDNSRecordNames[ 1 ] ) - 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .usRecordType = dnsTYPE_A_HOST, + .pcName = prvDNSRecordNames[ 0 ] + }; + prvDNSRecords[ 1 ] = ( DNSRecord_t ) { + .usRecordType = dnsTYPE_AAAA_HOST, + .pcName = prvDNSRecordNames[ 1 ] + }; + prvDNSRecordsLen = 2; +} + /* ============================= TEST CASES =============================== */ /** @@ -1352,6 +1382,8 @@ void test_DNS_ParseDNSReply_fail_empty_namefield( void ) uint8_t beg = sizeof( DNSMessage_t ); struct freertos_addrinfo * pxAddressInfo; uint16_t usPort = 80; + NetworkEndPoint_t xEndPoint = { 0 }; + NetworkBufferDescriptor_t xNetworkBuffer = { .pxEndPoint = &xEndPoint }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); pucUDPPayloadBuffer[ offsetof( DNSMessage_t, usQuestions ) ] = 4; @@ -1362,6 +1394,11 @@ void test_DNS_ParseDNSReply_fail_empty_namefield( void ) usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usType */ usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usClass */ + /* When the DNS layer chooses to fetch the network buffer is an implementation detail. + * And it's only needed for getting Endpoint data + */ + pxUDPPayloadBuffer_to_NetworkBuffer_IgnoreAndReturn( &xNetworkBuffer ); + ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, uxBufferLength, &pxAddressInfo, @@ -1693,11 +1730,11 @@ void test_DNS_ParseDNSReply_InvalidEndpointType( void ) usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ hook_return = pdTRUE; - pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( &pxNetworkBuffer ); - uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); - usGenerateChecksum_ExpectAnyArgsAndReturn( 555 ); - usGenerateProtocolChecksum_ExpectAnyArgsAndReturn( 444 ); - vReturnEthernetFrame_Expect( &pxNetworkBuffer, pdFALSE ); + + /* When the DNS layer chooses to fetch the network buffer is an implementation detail. + * And it's only needed for getting Endpoint data + */ + pxUDPPayloadBuffer_to_NetworkBuffer_IgnoreAndReturn( &pxNetworkBuffer ); ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, uxBufferLength, @@ -1752,6 +1789,15 @@ void test_DNS_ParseDNSReply_answer_record_too_many_answers( void ) usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usType */ usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usClass */ + + NetworkEndPoint_t xEndPoint = { 0 }; + NetworkBufferDescriptor_t xNetworkBuffer = { .pxEndPoint = &xEndPoint }; + + /* When the DNS layer chooses to fetch the network buffer is an implementation detail. + * And it's only needed for getting Endpoint data + */ + pxUDPPayloadBuffer_to_NetworkBuffer_IgnoreAndReturn( &xNetworkBuffer ); + usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usType */ ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, @@ -1774,7 +1820,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_xBufferAllocFixedsize( void ) uint8_t * pucUDPPayloadBuffer = udp_buffer + prvALIGNED_UDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; xBufferAllocFixedSize = pdTRUE; uint8_t * nullAddress = NULL; @@ -1852,7 +1898,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply( void ) uint8_t * pucUDPPayloadBuffer = udp_buffer + prvALIGNED_UDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -1925,7 +1971,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply2( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv6; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -1987,80 +2033,6 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply2( void ) ASSERT_DNS_QUERY_HOOK_NOT_CALLED(); } -/** - * @brief ensures that the ip set in Setup is passed to the network with - * vReturnEthernetFrame : uxUDPOffset != ipUDP_PAYLOAD_OFFSET_IPv4 || uxUDPOffset != ipUDP_PAYLOAD_OFFSET_IPv6 - */ -void test_DNS_ParseDNSReply_answer_lmmnr_reply3( void ) -{ - uint32_t ret = 0xDEADC0DE; - uint8_t udp_buffer[ 250 + ipUDP_PAYLOAD_OFFSET_IPv4 ] = { 0 }; - uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4 - 1; - size_t uxBufferLength = 250; - struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; - - memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); - - NetworkBufferDescriptor_t pxNetworkBuffer = { 0 }; - pxNetworkBuffer.pucEthernetBuffer = udp_buffer; - pxNetworkBuffer.xDataLength = uxBufferLength; - - char dns[ 64 ]; - memset( dns, 'a', 64 ); - dns[ 63 ] = 0; - BaseType_t xExpected = pdFALSE; - size_t beg = sizeof( DNSMessage_t ); - - DNSMessage_t * dns_header; - - dns_header = ( DNSMessage_t * ) pucUDPPayloadBuffer; - - dns_header->usQuestions = FreeRTOS_htons( 1 ); - dns_header->usAnswers = FreeRTOS_htons( 2 ); - dns_header->usFlags = dnsDNS_PORT; - - pucUDPPayloadBuffer[ beg ] = 38; - beg++; - strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); - beg += 38; - - beg += sizeof( uint32_t ); - - pucUDPPayloadBuffer[ beg ] = 38; - beg++; - strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); - beg += 38; - - pucUDPPayloadBuffer[ beg ] = 38; - beg++; - strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); - beg += 38; - - uint8_t * pucNewBuffer = NULL; - pucNewBuffer = &( pucUDPPayloadBuffer[ 0 ] ); - LLMNRAnswer_t * pxAnswer = ( LLMNRAnswer_t * ) &( pucNewBuffer[ 56 ] ); /* xOffset1 = 56 */ - - xEndPoint.ipv4_settings.ulIPAddress = 11; - - usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ - usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ - hook_return = pdTRUE; - pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( &pxNetworkBuffer ); - uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); - - catch_assert( ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, - uxBufferLength, - &pxAddressInfo, - xExpected, - usPort ) ); - - /* ret is not reassigned, because the function asserts. */ - TEST_ASSERT_EQUAL( 0xDEADC0DE, ret ); - ASSERT_DNS_QUERY_HOOK_NOT_CALLED(); -} - /** * @brief ensures that the ip set in Setup is passed to the network with * vReturnEthernetFrame : usType = dnsTYPE_AAAA_HOST @@ -2072,7 +2044,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_diffUsType( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2149,7 +2121,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_NullNetworkBuffer( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2219,24 +2191,22 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_NullNetworkBuffer( void ) * @brief ensures that the ip set in Setup is passed to the network with * vReturnEthernetFrame : usType = 0 */ -void test_DNS_ParseDNSReply_answer_lmmnr_reply4( void ) +void test_DNS_ParseDNSReply_answer_lmmnr_reply3( void ) { uint32_t ret; uint8_t udp_buffer[ 250 + ipUDP_PAYLOAD_OFFSET_IPv4 ] = { 0 }; uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); NetworkBufferDescriptor_t pxNetworkBuffer = { 0 }; pxNetworkBuffer.pucEthernetBuffer = udp_buffer; pxNetworkBuffer.xDataLength = uxBufferLength; - - NetworkBufferDescriptor_t pxNewBuffer; - pxNewBuffer.pucEthernetBuffer = udp_buffer; - pxNewBuffer.xDataLength = uxBufferLength; + pxNetworkBuffer.pxEndPoint = &xEndPoint; char dns[ 64 ]; memset( dns, 'a', 64 ); @@ -2273,8 +2243,14 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply4( void ) pucNewBuffer = &( pucUDPPayloadBuffer[ ipUDP_PAYLOAD_OFFSET_IPv4 ] ); LLMNRAnswer_t * pxAnswer = ( LLMNRAnswer_t * ) &( pucNewBuffer[ 56 ] ); /* xOffset1 = 56 */ - usChar2u16_ExpectAnyArgsAndReturn( 0 ); /* usType */ - usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + + /* When the DNS layer chooses to fetch the network buffer is an implementation detail. + * And it's only needed for getting Endpoint data + */ + pxUDPPayloadBuffer_to_NetworkBuffer_IgnoreAndReturn( &pxNetworkBuffer ); + + usChar2u16_ExpectAnyArgsAndReturn( 0 ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ hook_return = pdTRUE; ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, @@ -2290,24 +2266,22 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply4( void ) * @brief ensures that the ip set in Setup is passed to the network with * vReturnEthernetFrame : usClass != dnsCLASS_IN */ -void test_DNS_ParseDNSReply_answer_lmmnr_reply5( void ) +void test_DNS_ParseDNSReply_answer_lmmnr_reply4( void ) { uint32_t ret; uint8_t udp_buffer[ 250 + ipUDP_PAYLOAD_OFFSET_IPv4 ] = { 0 }; uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); NetworkBufferDescriptor_t pxNetworkBuffer = { 0 }; pxNetworkBuffer.pucEthernetBuffer = udp_buffer; pxNetworkBuffer.xDataLength = uxBufferLength; - - NetworkBufferDescriptor_t pxNewBuffer; - pxNewBuffer.pucEthernetBuffer = udp_buffer; - pxNewBuffer.xDataLength = uxBufferLength; + pxNetworkBuffer.pxEndPoint = &xEndPoint; char dns[ 64 ]; memset( dns, 'a', 64 ); @@ -2344,8 +2318,13 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply5( void ) pucNewBuffer = &( pucUDPPayloadBuffer[ ipUDP_PAYLOAD_OFFSET_IPv4 ] ); LLMNRAnswer_t * pxAnswer = ( LLMNRAnswer_t * ) &( pucNewBuffer[ 56 ] ); /* xOffset1 = 56 */ - usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ - usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usClass */ + /* When the DNS layer chooses to fetch the network buffer is an implementation detail. + * And it's only needed for getting Endpoint data + */ + pxUDPPayloadBuffer_to_NetworkBuffer_IgnoreAndReturn( &pxNetworkBuffer ); + + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usClass */ hook_return = pdTRUE; ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, @@ -2369,7 +2348,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_query_hook_false( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; struct freertos_addrinfo * pxAddressInfo; struct xNetworkEndPoint xEndPoint = { 0 }; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; memset( pucUDPPayloadBuffer, 0x0, 250 ); size_t uxBufferLength = 250; @@ -2441,7 +2420,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2470,6 +2449,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2515,8 +2496,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer2( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { .bits.bIPv6 = true }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2544,6 +2525,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2589,7 +2572,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; + uint16_t usPort = ipLLMNR_PORT; NetworkEndPoint_t xEndPoint = { 0 }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2631,6 +2614,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2681,8 +2666,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer2( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { .bits.bIPv6 = true }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2723,6 +2708,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2773,8 +2760,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer3( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { .bits.bIPv6 = true }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2815,6 +2802,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer3( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2864,8 +2853,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_fixed_buffer( void ) uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; size_t uxBufferLength = 250; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { .bits.bIPv6 = true }; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2905,6 +2894,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_fixed_buffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2954,8 +2945,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) /* Maximum UDP payload length is 1500 + 14 - 42 = 1472. */ size_t uxBufferLength = ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER - ipUDP_PAYLOAD_OFFSET_IPv4; struct freertos_addrinfo * pxAddressInfo; - uint16_t usPort = 80; - NetworkEndPoint_t xEndPoint = { 0 }; + uint16_t usPort = ipLLMNR_PORT; + NetworkEndPoint_t xEndPoint = { .bits.bIPv6 = true }; int i; memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); @@ -2996,6 +2987,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) dns_header->usAnswers = FreeRTOS_htons( 0 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecords( "FreeRTOSFreeRTOSFree" ); + /* First 5 queries have maximum length. */ /* DNS name field format requirements: @@ -3098,6 +3091,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_AAAA_HOST ); /* usType */ usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( &pxNetworkBuffer ); usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_AAAA_HOST ); /* usType */ usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_AAAA_HOST ); /* usType */ @@ -3110,7 +3104,6 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); hook_return = pdTRUE; - pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( &pxNetworkBuffer ); ret = DNS_ParseDNSReply( pucUDPPayloadBuffer, uxBufferLength, @@ -4002,14 +3995,12 @@ void test_parseDNSAnswer_remaining_lt_dnsanswerrecord( void ) TEST_ASSERT_EQUAL( 44, uxBytesRead ); } -DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - UBaseType_t * outLen ) -{ - static DNSRecord_t xRecord; +BaseType_t xApplicationNBNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + const char * pcName ) +{ hook_called = pdTRUE; - *outLen = 0; - return &xRecord; + return hook_return; } void test_prepareReplyDNSMessage_null_pointer( void ) @@ -4066,6 +4057,8 @@ void test_DNS_ParseDNSReply_mdns_request( void ) strcpy( pucPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); beg += 38 + 1 + 4; /* Skip name, nul-byte, and Type/Class. */ + SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + /* xApplicationDNSQueryHook_Multi() must be called. */ hook_return = pdTRUE; /* it hasn't been called yet. */ From 64e6fbf4d96a7c854a6319f7b88c2c4655b1b1d5 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Tue, 17 Mar 2026 12:12:58 +0100 Subject: [PATCH 06/10] Full coverage of DNS_Parser unit tests --- source/FreeRTOS_DNS_Parser.c | 198 +++-- .../FreeRTOS_DNS_Parser_utest.c | 729 +++++++++++++++++- 2 files changed, 843 insertions(+), 84 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 334b18ca14..3d7f404b53 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -239,11 +239,12 @@ #if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) /** - * @brief Local implementation of posix strncasecmp + * @brief Check two strings for equality, up to the given length, with + * ascii case-insensitivity. */ - static int local_strncasecmp( const char * s1, - const char * s2, - size_t n ) + static BaseType_t prvCompareStringsCaseInsensitive( const char * s1, + const char * s2, + size_t n ) { while( n-- != 0 ) { @@ -262,21 +263,16 @@ if( c1 > c2 ) { - return 1; + return pdFALSE; } if( c2 > c1 ) { - return -1; - } - - if( c1 == '\0' ) - { - break; + return pdFALSE; } } - return 0; + return pdTRUE; } /** @@ -288,6 +284,9 @@ * pcDNSMessage <= pcDNSString < pcDNSMessageEnd must hold. If following labels * would walk past pcDNSMessageEnd, an error will be returned. * + * It should have been previously checked that the name pointed at by + * pcDNSString is valid. Either a 2 byte offset field or a full label sequence. + * * @param[in] pcDNSString Pointer to the DNS label string. * @param[in] pcDNSMessage Pointer to the start of the DNS payload. * @param[in] pcDNSMessageEnd The pointer to one-past-the-end of the DNS payload. @@ -302,15 +301,15 @@ { uint8_t const * pcDnsSegment = pcDNSString; char const * pcDotSegment = pcDotString; - UBaseType_t uxNumPointerFollows = 5; + BaseType_t uxAlreadyFollowedPointer = pdFALSE; - configASSERT( pcDNSString >= pcDNSMessage ); - configASSERT( pcDNSString < pcDNSMessageEnd ); + configASSERT( pcDNSString >= pcDNSMessage ); /* LCOV_EXCL_BR_LINE */ + configASSERT( pcDNSString < pcDNSMessageEnd ); /* LCOV_EXCL_BR_LINE */ for( ; ; ) { UBaseType_t const uxSegmentLength = ( UBaseType_t ) ( *pcDnsSegment ); - int ulComparison; + BaseType_t ulComparison; if( uxSegmentLength == 0 ) { @@ -325,7 +324,7 @@ uint16_t usLocation; - if( uxNumPointerFollows == 0 ) + if( uxAlreadyFollowedPointer == pdTRUE ) { /* We have followed too many pointers, the message is probably malformed. */ return pdFALSE; @@ -334,13 +333,6 @@ /* This is a pointer to another location in the DNS message. * Follow the pointer and compare the rest of the string there. */ usLocation = ( pcDnsSegment[ 0 ] & ~dnsNAME_IS_OFFSET ) << 8; - - if( ( pcDnsSegment + 1 ) >= pcDNSMessageEnd ) - { - /* Reading one further would go past the end of the message */ - return pdFALSE; - } - usLocation |= ( uint16_t ) pcDnsSegment[ 1 ]; if( usLocation >= ( pcDnsSegment - pcDNSMessage ) ) @@ -350,7 +342,7 @@ } pcDnsSegment = pcDNSMessage + usLocation; - uxNumPointerFollows--; + uxAlreadyFollowedPointer = pdTRUE; continue; } @@ -389,9 +381,9 @@ } /* The dot string should have a segment of the same length at this point. */ - ulComparison = local_strncasecmp( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); + ulComparison = prvCompareStringsCaseInsensitive( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); - if( ulComparison != 0 ) + if( ulComparison == pdFALSE ) { return pdFALSE; } @@ -709,9 +701,12 @@ { if( xEndPointValid == pdFALSE ) { - if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) + /* In the unit tests, we use the IPv6 compatible hook that fetches the + * endpoint as well, which is called above. That means that by the time + * we get here, DNS_GetEndpoint will always succeed */ + if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) /* LCOV_EXCL_BR_LINE */ { - return pdFALSE; + return pdFALSE; /* LCOV_EXCL_LINE */ } xEndPointValid = pdTRUE; @@ -905,11 +900,23 @@ BaseType_t uxLength; UBaseType_t uxUDPOffset; NetworkBufferDescriptor_t * pxNetworkBuffer; + + /* In the unit tests, we use the IPv6 compatible hook that fetches the + * endpoint, and therefore also the network buffer. We called that during + * the question parsing, so by the time we get here DNS_GetNetworkBuffer + * will always succeed */ pxNetworkBuffer = DNS_GetNetworkBuffer( &xSet ); - /* This test could be replaced with a assert(). */ - configASSERT( pxNetworkBuffer != NULL ); + + if( pxNetworkBuffer == NULL ) /* LCOV_EXCL_BR_LINE */ + { + /* LCOV_EXCL_START */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to get network buffer\n" ) ); + break; + /* LCOV_EXCL_STOP */ + } + uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); - configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); + configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); /* LCOV_EXCL_BR_LINE */ uxDataLength = uxBufferLength + sizeof( UDPHeader_t ) + @@ -926,14 +933,7 @@ continue; } - uxNumAnswers++; - uxExtraSize += strlen( pRecord->pcName ) + 2; /* Name */ - uxExtraSize += 2; /* Type */ - uxExtraSize += 2; /* Class */ - uxExtraSize += 4; /* TTL */ - uxExtraSize += 2; /* RDLENGTH */ - - switch( pRecord->usRecordType ) + switch( pRecord->usRecordType ) /* LCOV_EXCL_BR_LINE The default case is not reached */ { #if ( ipconfigUSE_IPv4 != 0 ) case dnsTYPE_A_HOST: @@ -955,19 +955,33 @@ break; case dnsTYPE_TXT: - /* TXT records don't use length-label strings, so it's not +2. */ - /* Just a length field and no null terminator. So it's +1. */ - uxExtraSize += strlen( pRecord->xData.pcTxtRecord ) + 1; /* Text. */ - break; + { + size_t const uxTextLength = strlen( pRecord->xData.pcTxtRecord ); + /* TXT records don't use length-label strings, so it's not +2. */ + /* Just a length field and no null terminator. So it's +1. */ + uxExtraSize += uxTextLength + 1; /* Text. */ + break; + } case dnsTYPE_PTR: uxExtraSize += strlen( pRecord->xData.pcPtrRecord ) + 2; /* Domain; */ break; + /* This region is unreachable in practice, because we already filtered the records in the question + * parsing */ + /* LCOV_EXCL_START */ default: FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pRecord->usRecordType ) ); - break; + continue; + /* LCOV_EXCL_STOP */ } + + /* Common elements */ + uxExtraSize += strlen( pRecord->pcName ) + 2; /* Name */ + uxExtraSize += 2; /* Type */ + uxExtraSize += 2; /* Class */ + uxExtraSize += 4; /* TTL */ + uxExtraSize += 2; /* RDLENGTH */ } if( xBufferAllocFixedSize == pdFALSE ) @@ -1023,7 +1037,7 @@ DNSMessage_t, usFlags, xSet.usPortNumber == ipLLMNR_PORT ? dnsLLMNR_FLAGS_IS_RESPONSE : dnsMDNS_FLAGS_IS_RESPONSE ); /* Set the response flag */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, uxNumAnswers ); + /* Number of answers will be filled later */ vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, 0 ); @@ -1033,53 +1047,66 @@ { DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; MDNSResponseMiddle_t * middle; + uint8_t * pucInterimWrite = xSet.pucByte; if( record->uxIncludeInAnswer == pdFALSE ) { continue; } - if( !DNS_WriteName( ( char * ) xSet.pucByte, record->pcName ) ) + /* Exclude from coverage because this should be impossible to fail. + * We already checked the name during question parsing. */ + if( !DNS_WriteName( ( char * ) pucInterimWrite, record->pcName ) ) /* LCOV_EXCL_BR_LINE */ { - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write name %s\n", record->pcName ) ); + /* LCOV_EXCL_START */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write record name" ) ); /* This should not happen, since we have already calculated the required size. */ break; + /* LCOV_EXCL_STOP */ } - xSet.pucByte += strlen( record->pcName ) + 2; - middle = ( MDNSResponseMiddle_t * ) xSet.pucByte; + /* Interim write pointer that we will only "commit" once everything is done */ + pucInterimWrite += strlen( record->pcName ) + 2; + middle = ( MDNSResponseMiddle_t * ) pucInterimWrite; vSetField16( middle, MDNSResponseMiddle_t, usType, record->usRecordType ); vSetField16( middle, MDNSResponseMiddle_t, usClass, dnsCLASS_IN ); /* 1: Class IN */ vSetField32( middle, MDNSResponseMiddle_t, ulTTL, dnsLLMNR_TTL_VALUE ); - switch( record->usRecordType ) + switch( record->usRecordType ) /* LCOV_EXCL_BR_LINE The default case is not reached */ { #if ( ipconfigUSE_IPv4 != 0 ) case dnsTYPE_A_HOST: { MDNSResponseHostAEnd_t * host_end; vSetField16( middle, MDNSResponseMiddle_t, usDataLength, 4 ); - xSet.pucByte += sizeof( *middle ); - host_end = ( MDNSResponseHostAEnd_t * ) xSet.pucByte; + pucInterimWrite += sizeof( *middle ); + host_end = ( MDNSResponseHostAEnd_t * ) pucInterimWrite; vSetField32( host_end, MDNSResponseHostAEnd_t, ipAddr, FreeRTOS_ntohl( pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress ) ); - xSet.pucByte += sizeof( *host_end ); + pucInterimWrite += sizeof( *host_end ); break; } #endif /* ipconfigUSE_IPv4 */ #if ( ipconfigUSE_IPv6 != 0 ) case dnsTYPE_AAAA_HOST: vSetField16( middle, MDNSResponseMiddle_t, usDataLength, ipSIZE_OF_IPv6_ADDRESS ); - xSet.pucByte += sizeof( *middle ); - ( void ) memcpy( xSet.pucByte, pxNetworkBuffer->pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); - xSet.pucByte += ipSIZE_OF_IPv6_ADDRESS; + pucInterimWrite += sizeof( *middle ); + ( void ) memcpy( pucInterimWrite, pxNetworkBuffer->pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + pucInterimWrite += ipSIZE_OF_IPv6_ADDRESS; break; #endif /* ipconfigUSE_IPv6 */ case dnsTYPE_PTR: vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcPtrRecord ) + 2 ); - xSet.pucByte += sizeof( *middle ); - DNS_WriteName( ( char * ) xSet.pucByte, record->xData.pcPtrRecord ); - xSet.pucByte += strlen( record->xData.pcPtrRecord ) + 2; + pucInterimWrite += sizeof( *middle ); + + if( DNS_WriteName( ( char * ) pucInterimWrite, record->xData.pcPtrRecord ) == pdFALSE ) + { + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write PTR record" ) ); + /* Skip this record */ + continue; + } + + pucInterimWrite += strlen( record->xData.pcPtrRecord ) + 2; break; case dnsTYPE_SRV: @@ -1090,14 +1117,21 @@ MDNSResponseMiddle_t, usDataLength, sizeof( MDNSResponseSRVEnd_t ) + strlen( record->xData.xSrvRecord.pcTarget ) + 2 ); - xSet.pucByte += sizeof( *middle ); - srv_end = ( MDNSResponseSRVEnd_t * ) xSet.pucByte; + pucInterimWrite += sizeof( *middle ); + srv_end = ( MDNSResponseSRVEnd_t * ) pucInterimWrite; vSetField16( srv_end, MDNSResponseSRVEnd_t, priority, 0 ); vSetField16( srv_end, MDNSResponseSRVEnd_t, weight, 0 ); vSetField16( srv_end, MDNSResponseSRVEnd_t, port, record->xData.xSrvRecord.usPort ); - xSet.pucByte += sizeof( *srv_end ); - DNS_WriteName( ( char * ) xSet.pucByte, record->xData.xSrvRecord.pcTarget ); - xSet.pucByte += strlen( record->xData.xSrvRecord.pcTarget ) + 2; + pucInterimWrite += sizeof( *srv_end ); + + if( DNS_WriteName( ( char * ) pucInterimWrite, record->xData.xSrvRecord.pcTarget ) == pdFALSE ) + { + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write SRV record target name" ) ); + /* Skip this record */ + continue; + } + + pucInterimWrite += strlen( record->xData.xSrvRecord.pcTarget ) + 2; break; } @@ -1108,21 +1142,40 @@ if( xTextLength > 255 ) { /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: TXT record too long: %s\n", record->xData.pcTxtRecord ) ); - xTextLength = 0; - break; + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write TXT record" ) ); + continue; } vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcTxtRecord ) + 1 ); - xSet.pucByte += sizeof( *middle ); - *xSet.pucByte++ = ( uint8_t ) xTextLength; - memcpy( xSet.pucByte, record->xData.pcTxtRecord, xTextLength ); - xSet.pucByte += xTextLength; + pucInterimWrite += sizeof( *middle ); + *pucInterimWrite++ = ( uint8_t ) xTextLength; + memcpy( pucInterimWrite, record->xData.pcTxtRecord, xTextLength ); + pucInterimWrite += xTextLength; break; } + + /* This region is unreachable in practice, because we already filtered the records in the question + * parsing */ + /* LCOV_EXCL_START */ + default: + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", record->usRecordType ) ); + continue; + /* LCOV_EXCL_STOP */ } + + xSet.pucByte = pucInterimWrite; + uxNumAnswers++; } + if( uxNumAnswers == 0U ) + { + /* No answers ended up in the message, that means some of our records + * couldn't be written out. Just break, no need to reply. */ + break; + } + + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, uxNumAnswers ); + if( xSet.usPortNumber == ipLLMNR_PORT ) { uxIsLLMNR = pdTRUE; @@ -1159,7 +1212,6 @@ #endif /* ipconfigUSE_LLMNR == 1 || ipconfigUSE_MDNS == 1 */ } while( ipFALSE_BOOL ); - /* coverity[deadcode] */ if( pxNewBuffer != NULL ) { vReleaseNetworkBufferAndDescriptor( pxNewBuffer ); diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index 0f9bd214e9..34160a8bdc 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -70,8 +70,8 @@ static int callback_called = 0; static BaseType_t hook_return = pdFALSE; static BaseType_t hook_called = pdFALSE; -static char prvDNSRecordNames[ 2 ][ 256 ]; -static DNSRecord_t prvDNSRecords[ 2 ]; +static char prvDNSRecordNames[ 16 ][ 256 ]; +static DNSRecord_t prvDNSRecords[ 16 ]; static UBaseType_t prvDNSRecordsLen = 0; /* =========================== STATIC FUNCTIONS =========================== */ @@ -132,7 +132,7 @@ DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * px return prvDNSRecords; } -static void SetDNSRecords( char const * pcName ) +static void SetDNSRecordsSimple( char const * pcName ) { strncpy( prvDNSRecordNames[ 0 ], pcName, sizeof( prvDNSRecordNames[ 0 ] ) - 1 ); strncpy( prvDNSRecordNames[ 1 ], pcName, sizeof( prvDNSRecordNames[ 1 ] ) - 1 ); @@ -2449,7 +2449,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2525,7 +2525,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2614,7 +2614,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2708,7 +2708,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2802,7 +2802,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer3( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2894,7 +2894,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_fixed_buffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); pucUDPPayloadBuffer[ beg ] = 38; beg++; @@ -2987,7 +2987,7 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) dns_header->usAnswers = FreeRTOS_htons( 0 ); dns_header->usFlags = dnsDNS_PORT; - SetDNSRecords( "FreeRTOSFreeRTOSFree" ); + SetDNSRecordsSimple( "FreeRTOSFreeRTOSFree" ); /* First 5 queries have maximum length. */ @@ -3115,6 +3115,713 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) ASSERT_DNS_QUERY_HOOK_CALLED(); } +typedef struct +{ + uint8_t * pucUDPPayloadBuffer; + size_t uxBufferLength; + struct freertos_addrinfo * pxAddressInfo; + uint16_t usPort; + NetworkBufferDescriptor_t * pxNetworkBuffer; + uint8_t * write_head; + size_t uxNumQuestions; +} DnsQTestData_t; + +DnsQTestData_t const dns_q_test_init( size_t uxNumQuestions ) +{ + static uint8_t udp_buffer[ ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER ] = { 0 }; + static NetworkEndPoint_t xEndPoint = { 0 }; + static NetworkBufferDescriptor_t pxNetworkBuffer = { 0 }; + static struct freertos_addrinfo * pxAddressInfo; + + uint8_t * pucUDPPayloadBuffer = ( ( uint8_t * ) udp_buffer ) + ipUDP_PAYLOAD_OFFSET_IPv4; + /* Maximum UDP payload length is 1500 + 14 - 42 = 1472. */ + size_t uxBufferLength = ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER - ipUDP_PAYLOAD_OFFSET_IPv4; + uint16_t usPort = ipMDNS_PORT; + int i; + + memset( pucUDPPayloadBuffer, 0x00, uxBufferLength ); + + xBufferAllocFixedSize = pdTRUE; + + xEndPoint.ipv4_settings.ulIPAddress = 0xABCD1234; + pxNetworkBuffer.pucEthernetBuffer = udp_buffer; + pxNetworkBuffer.xDataLength = uxBufferLength; + pxNetworkBuffer.pxEndPoint = &xEndPoint; + + UDPPacket_t * pxUDPPacket; + IPHeader_t * pxIPHeader; + UDPHeader_t * pxUDPHeader; + + pxUDPPacket = ( ( UDPPacket_t * ) + pxNetworkBuffer.pucEthernetBuffer ); + pxIPHeader = &pxUDPPacket->xIPHeader; + pxIPHeader->ucVersionHeaderLength = 0x0; + pxUDPHeader = &pxUDPPacket->xUDPHeader; + IPPacket_t * xIPPacket = ( ( IPPacket_t * ) pxNetworkBuffer.pucEthernetBuffer ); + + pxIPHeader->ulSourceIPAddress = 1234; + + BaseType_t xExpected = pdFALSE; + + DNSMessage_t * dns_header; + + dns_header = ( DNSMessage_t * ) pucUDPPayloadBuffer; + memset( dns_header, 0x00, sizeof( DNSMessage_t ) ); + dns_header->usQuestions = FreeRTOS_htons( uxNumQuestions ); + dns_header->usAnswers = FreeRTOS_htons( 0 ); + dns_header->usFlags = dnsDNS_PORT; + + return ( DnsQTestData_t ) { + .pucUDPPayloadBuffer = pucUDPPayloadBuffer, + .uxBufferLength = uxBufferLength, + .pxAddressInfo = pxAddressInfo, + .usPort = usPort, + .pxNetworkBuffer = &pxNetworkBuffer, + .write_head = pucUDPPayloadBuffer + sizeof( DNSMessage_t ), + .uxNumQuestions = uxNumQuestions + }; +} + +#define PUSH_LABEL( buff, data ) \ + do { \ + size_t const len = sizeof( data ) - 1; \ + buff[ 0 ] = ( uint8_t ) len; \ + memcpy( buff + 1, data, len ); \ + buff += ( len + 1 ); \ + } while( 0 ) + +#define END_LABELS( buff ) \ + do { \ + buff[ 0 ] = 0; \ + buff++; \ + } while( 0 ) + +#define A_TYPE_IN_CLASS( buff ) \ + do { \ + memcpy( buff, "\x00\x01\00\01", 4 ); \ + buff += 4; \ + } while( 0 ) + +#define PUSH_OFFSET( buff, offset ) \ + do { \ + buff[ 0 ] = 0xC0; \ + buff[ 1 ] = offset; \ + buff += 2; \ + } while( 0 ) + +void static expect_dns_result( DnsQTestData_t * test_data, + BaseType_t uxShouldSucceed, + uint16_t usType ) +{ + if( test_data->uxNumQuestions > 0 ) + { + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( usType ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data->pxNetworkBuffer ); + + for( i = 0; i < test_data->uxNumQuestions - 1; i++ ) + { + usChar2u16_ExpectAnyArgsAndReturn( usType ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + } + } + + if( uxShouldSucceed ) + { + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + usGenerateChecksum_ExpectAnyArgsAndReturn( 555 ); + usGenerateProtocolChecksum_ExpectAnyArgsAndReturn( 444 ); + vReturnEthernetFrame_Expect( test_data->pxNetworkBuffer, pdFALSE ); + } + + BaseType_t ret = DNS_ParseDNSReply( test_data->pucUDPPayloadBuffer, + test_data->write_head - test_data->pucUDPPayloadBuffer, + &test_data->pxAddressInfo, + pdFALSE, + test_data->usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_case_insensitivity( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "fREErtos" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_multi_label( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS.TCP" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + size_t last_offset = test_data.write_head - test_data.pucUDPPayloadBuffer; + PUSH_LABEL( test_data.write_head, "TCP" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_comparison_negative( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 2 ); + + SetDNSRecordsSimple( "F@eeRTOS" ); + + PUSH_LABEL( test_data.write_head, "F@efRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + PUSH_LABEL( test_data.write_head, "F@edRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_comparison_early_termination( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 3 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + /* Q1 says it has length 10, but the string is null terminated after 8. + * This matches our record, but the length is wrong. It should fail */ + PUSH_LABEL( test_data.write_head, "FreeRTOS\0\0" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + /* Q2 has length 4 that is a prefix of our record, but isn't long enough */ + PUSH_LABEL( test_data.write_head, "Free" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + /* Q3 has our record as a suffix, but is too long */ + PUSH_LABEL( test_data.write_head, "FreeRTOStcp" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_follow_offset( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 2 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "prefix" ); + size_t const offset = test_data.write_head - test_data.pucUDPPayloadBuffer; + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + PUSH_OFFSET( test_data.write_head, offset ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_offset_oob( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 2 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "0" ); + size_t const offset = ( test_data.write_head - test_data.pucUDPPayloadBuffer ) - 1; + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + /* This offset points to the 0 in the previous record, ascii 48 + * If this were to be followed as an actual label length, we would + * walk past the end of the DNS record. That must be caught. + */ + + PUSH_OFFSET( test_data.write_head, offset ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_label_goes_forward( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_OFFSET( test_data.write_head, 0 ); + uint8_t * offset_loc = test_data.write_head - 1; + A_TYPE_IN_CLASS( test_data.write_head ); + /* Make the previous question point forward to this future, matching question */ + *offset_loc = test_data.write_head - test_data.pucUDPPayloadBuffer; + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + + +void test_DNS_ParseDNSReply_questions_input_segment_too_long( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + /* Label length is 26 * 3 == 78, which is illegally long */ + PUSH_LABEL( test_data.write_head, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_nested_follows( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 3 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "prefix" ); + size_t last_offset = test_data.write_head - test_data.pucUDPPayloadBuffer; + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + size_t offset_of_offset = test_data.write_head - test_data.pucUDPPayloadBuffer; + PUSH_OFFSET( test_data.write_head, last_offset ); + A_TYPE_IN_CLASS( test_data.write_head ); + + PUSH_OFFSET( test_data.write_head, offset_of_offset ); + A_TYPE_IN_CLASS( test_data.write_head ); + + /* This will still work, because the first offset is a valid question. + */ + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_srv_record( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_SRV, + .xData.xSrvRecord.pcTarget = "TCP", .xData.xSrvRecord.usPort = 1234 + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_SRV ); +} + +void test_DNS_ParseDNSReply_questions_txt_record( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_TXT, + .xData.pcTxtRecord = "Some Text" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_TXT ); +} + +void test_DNS_ParseDNSReply_questions_txt_record_too_long( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_TXT, + .xData.pcTxtRecord = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_TXT ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_ptr_record( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_PTR, + .xData.pcPtrRecord = "TCP" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_PTR ); +} + +void test_DNS_ParseDNSReply_questions_ptr_record_matches_any( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_PTR, + .xData.pcPtrRecord = "TCP" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_ANY ); +} + +void test_DNS_ParseDNSReply_questions_unsupported_record( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_PTR, + .xData.pcPtrRecord = "TCP" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdFALSE, 0x0D ); +} + +void test_DNS_ParseDNSReply_questions_unsupported_record_in_hook( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = 0x0D, + .xData.pcPtrRecord = "TCP" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_no_ipv4_address( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + test_data.pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress = 0; + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_no_ipv6_address( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + test_data.pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress = 0; + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x1C\x00\x01", 4 ); /* AAAA record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdFALSE, dnsTYPE_AAAA_HOST ); +} + +void test_DNS_ParseDNSReply_questions_ipv4_when_ipv6( void ) +{ + /* If we are asked for our ipv4 record but we're on ipv6, we should + * still serve it. + */ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + test_data.pxNetworkBuffer->pxEndPoint->bits.bIPv6 = pdTRUE; + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_missing_endpoint( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + test_data.pxNetworkBuffer->pxEndPoint = NULL; + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_NOT_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_fixed_size_not_big_enough( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + xBufferAllocFixedSize = true; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + ipconfigNETWORK_MTU, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_wrong_port( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + + test_data.usPort = 1234; /* Some garbage port */ + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + /* usGenerateChecksum_ExpectAnyArgsAndReturn( 555 ); */ + /* usGenerateProtocolChecksum_ExpectAnyArgsAndReturn( 444 ); */ + /* vReturnEthernetFrame_Expect( test_data.pxNetworkBuffer, pdFALSE ); */ + + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + + +void test_DNS_ParseDNSReply_questions_ptr_output_segment_too_long( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_PTR, + /* This is longer than 63 so it will be rejected*/ + .xData.pcPtrRecord = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x0C\x00\x01", 4 ); /* PTR record, IN class */ + test_data.write_head += 4; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_PTR ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_srv_output_segment_too_long( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_SRV, + /* This is longer than 63 so it will be rejected*/ + .xData.xSrvRecord.pcTarget = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + .xData.xSrvRecord.usPort = 1234 + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x0C\x00\x01", 4 ); /* PTR record, IN class */ + test_data.write_head += 4; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_SRV ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_srv_output_segment_zero_len( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_SRV, + /* The middle segment is len 0 so it will be rejected*/ + .xData.xSrvRecord.pcTarget = "a..b", + .xData.xSrvRecord.usPort = 1234 + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x0C\x00\x01", 4 ); /* PTR record, IN class */ + test_data.write_head += 4; + + UBaseType_t i; + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_SRV ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); + + uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); + + BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, + test_data.write_head - test_data.pucUDPPayloadBuffer, + &test_data.pxAddressInfo, + pdFALSE, + test_data.usPort ); + TEST_ASSERT_EQUAL( pdFALSE, ret ); + ASSERT_DNS_QUERY_HOOK_CALLED(); +} + +void test_DNS_ParseDNSReply_questions_multimatch( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + SetDNSRecordsSimple( "FreeRTOS" ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\xFF\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_ANY_HOST ); +} + /** * @brief ensures that when the number of answers is zero no packet is sent over * the network @@ -4057,7 +4764,7 @@ void test_DNS_ParseDNSReply_mdns_request( void ) strcpy( pucPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); beg += 38 + 1 + 4; /* Skip name, nul-byte, and Type/Class. */ - SetDNSRecords( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); /* xApplicationDNSQueryHook_Multi() must be called. */ hook_return = pdTRUE; From 7e9afdfa2cf66064c76ac0c23f42bf7541b32a33 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Wed, 18 Mar 2026 09:10:48 +0100 Subject: [PATCH 07/10] Support Additional Records --- source/FreeRTOS_DNS_Parser.c | 295 +++++++++++------- source/include/FreeRTOS_DNS_Globals.h | 19 +- test/build-combination/Common/main.c | 4 + .../FreeRTOS_DNS_Parser_utest.c | 79 +++++ 4 files changed, 278 insertions(+), 119 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 3d7f404b53..35aebe231b 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -520,13 +520,154 @@ for( x = 0U; x < xSet->uxDNSRecordCount; x++ ) { - xSet->pxDNSRecords[ x ].uxIncludeInAnswer = pdFALSE; + xSet->pxDNSRecords[ x ].uxServeRecord = dnsRECORD_SERVE_NO; } } #endif /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ return pdTRUE; } +/** @brief Write a DNS record into the response buffer. + * + * @param[in,out] xSet A set of variables that are shared among the helper functions. + * @param[in] pxRecord The DNS record to write. + * @returns pdTRUE if the record was successfully written, pdFALSE otherwise. + * In the pdFALSE case, xSet.pucByte will be unchanged. + */ + static BaseType_t prvWriteDNSRecord( ParseSet_t * xSet, + DNSRecord_t const * pxRecord ) + { + uint8_t * pucWriteHead; + MDNSResponseMiddle_t * middle; + NetworkBufferDescriptor_t * pxNetworkBuffer; + + pucWriteHead = xSet->pucByte; + pxNetworkBuffer = DNS_GetNetworkBuffer( xSet ); + + /* By the time we get here we should already have a valid network buffer, + * so this should never fail*/ + if( pxNetworkBuffer == NULL ) /* LCOV_EXCL_BR_LINE */ + { + /* LCOV_EXCL_START */ + FreeRTOS_printf( ( "prvWriteDNSRecord: Failed to get network buffer\n" ) ); + return pdFALSE; + /* LCOV_EXCL_STOP */ + } + + /* Exclude from coverage because this should be impossible to fail. + * We already checked the name during question parsing. */ + if( !DNS_WriteName( ( char * ) pucWriteHead, pxRecord->pcName ) ) /* LCOV_EXCL_BR_LINE */ + { + /* LCOV_EXCL_START */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write record name" ) ); + /* This should not happen, since we have already calculated the required size. */ + return pdFALSE; + /* LCOV_EXCL_STOP */ + } + + /* Interim write pointer that we will only "commit" once everything is done */ + pucWriteHead += strlen( pxRecord->pcName ) + 2; + middle = ( MDNSResponseMiddle_t * ) pucWriteHead; + + vSetField16( middle, MDNSResponseMiddle_t, usType, pxRecord->usRecordType ); + vSetField16( middle, MDNSResponseMiddle_t, usClass, dnsCLASS_IN ); /* 1: Class IN */ + vSetField32( middle, MDNSResponseMiddle_t, ulTTL, dnsLLMNR_TTL_VALUE ); + + switch( pxRecord->usRecordType ) /* LCOV_EXCL_BR_LINE The default case is not reached */ + { + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + { + MDNSResponseHostAEnd_t * host_end; + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, 4 ); + pucWriteHead += sizeof( *middle ); + host_end = ( MDNSResponseHostAEnd_t * ) pucWriteHead; + vSetField32( host_end, MDNSResponseHostAEnd_t, ipAddr, FreeRTOS_ntohl( pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress ) ); + pucWriteHead += sizeof( *host_end ); + break; + } + #endif /* ipconfigUSE_IPv4 */ + #if ( ipconfigUSE_IPv6 != 0 ) + case dnsTYPE_AAAA_HOST: + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, ipSIZE_OF_IPv6_ADDRESS ); + pucWriteHead += sizeof( *middle ); + ( void ) memcpy( pucWriteHead, pxNetworkBuffer->pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); + pucWriteHead += ipSIZE_OF_IPv6_ADDRESS; + break; + #endif /* ipconfigUSE_IPv6 */ + case dnsTYPE_PTR: + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( pxRecord->xData.pcPtrRecord ) + 2 ); + pucWriteHead += sizeof( *middle ); + + if( DNS_WriteName( ( char * ) pucWriteHead, pxRecord->xData.pcPtrRecord ) == pdFALSE ) + { + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write PTR record" ) ); + /* Skip this record */ + return pdFALSE; + } + + pucWriteHead += strlen( pxRecord->xData.pcPtrRecord ) + 2; + break; + + case dnsTYPE_SRV: + { + MDNSResponseSRVEnd_t * srv_end; + vSetField16( + middle, + MDNSResponseMiddle_t, + usDataLength, + sizeof( MDNSResponseSRVEnd_t ) + strlen( pxRecord->xData.xSrvRecord.pcTarget ) + 2 ); + pucWriteHead += sizeof( *middle ); + srv_end = ( MDNSResponseSRVEnd_t * ) pucWriteHead; + vSetField16( srv_end, MDNSResponseSRVEnd_t, priority, 0 ); + vSetField16( srv_end, MDNSResponseSRVEnd_t, weight, 0 ); + vSetField16( srv_end, MDNSResponseSRVEnd_t, port, pxRecord->xData.xSrvRecord.usPort ); + pucWriteHead += sizeof( *srv_end ); + + if( DNS_WriteName( ( char * ) pucWriteHead, pxRecord->xData.xSrvRecord.pcTarget ) == pdFALSE ) + { + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write SRV record target name" ) ); + /* Skip this record */ + return pdFALSE; + } + + pucWriteHead += strlen( pxRecord->xData.xSrvRecord.pcTarget ) + 2; + break; + } + + case dnsTYPE_TXT: + { + size_t xTextLength = strlen( pxRecord->xData.pcTxtRecord ); + + if( xTextLength > 255 ) + { + /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write TXT record" ) ); + return pdFALSE; + } + + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( pxRecord->xData.pcTxtRecord ) + 1 ); + pucWriteHead += sizeof( *middle ); + *pucWriteHead++ = ( uint8_t ) xTextLength; + memcpy( pucWriteHead, pxRecord->xData.pcTxtRecord, xTextLength ); + pucWriteHead += xTextLength; + break; + } + + /* This region is unreachable in practice, because we already filtered the records in the question + * parsing */ + /* LCOV_EXCL_START */ + default: + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", pxRecord->usRecordType ) ); + return pdFALSE; + /* LCOV_EXCL_STOP */ + } + + xSet->pucByte = pucWriteHead; + return pdTRUE; + } + + #endif /* if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) */ @@ -642,7 +783,7 @@ ( DNSRecord_t ) { .usRecordType = dnsTYPE_AAAA_HOST, .pcName = xSet->pcName, - .uxIncludeInAnswer = pdTRUE, + .uxServeRecord = dnsRECORD_SERVE_ANSWER, }; } #endif /* if ( ( ipconfigUSE_IPv6 != 0 ) ) */ @@ -656,7 +797,7 @@ ( DNSRecord_t ) { .usRecordType = dnsTYPE_A_HOST, .pcName = xSet->pcName, - .uxIncludeInAnswer = pdTRUE, + .uxServeRecord = dnsRECORD_SERVE_ANSWER, }; } #endif /* if ( ( ipconfigUSE_IPv4 != 0 ) ) */ @@ -724,10 +865,15 @@ continue; } - pRecord->uxIncludeInAnswer = pdTRUE; + pRecord->uxServeRecord = dnsRECORD_SERVE_ANSWER; xSet->xDNSRecordsMatched = pdTRUE; } } + + if( xSet->xDNSRecordsMatched ) + { + xApplicationDNSRecordsMatchedHook(); + } #endif /* if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) */ } #endif /* ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ @@ -745,6 +891,7 @@ return pdTRUE; } + /** * @brief Process a response packet from a DNS server, or an LLMNR/MDNS reply. * @@ -894,8 +1041,9 @@ UBaseType_t uxExtraSize = 0; UBaseType_t uxDataLength; UBaseType_t uxNumAnswers = 0; + UBaseType_t uxNumAdditionalRRs = 0; uint8_t * pucNewBuffer = NULL; - uint8_t * start_of_dns_answers; + uint8_t * pucStartOfAnswers; UBaseType_t uxIsLLMNR; BaseType_t uxLength; UBaseType_t uxUDPOffset; @@ -928,7 +1076,7 @@ { DNSRecord_t const * pRecord = &xSet.pxDNSRecords[ i ]; - if( pRecord->uxIncludeInAnswer == pdFALSE ) + if( pRecord->uxServeRecord == dnsRECORD_SERVE_NO ) { continue; } @@ -1039,132 +1187,44 @@ xSet.usPortNumber == ipLLMNR_PORT ? dnsLLMNR_FLAGS_IS_RESPONSE : dnsMDNS_FLAGS_IS_RESPONSE ); /* Set the response flag */ /* Number of answers will be filled later */ vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAuthorityRRs, 0 ); /* No authority */ - vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, 0 ); + /* Number of additional records will be filled later */ - start_of_dns_answers = xSet.pucByte; + pucStartOfAnswers = xSet.pucByte; for( i = 0; i < xSet.uxDNSRecordCount; i++ ) { DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; - MDNSResponseMiddle_t * middle; - uint8_t * pucInterimWrite = xSet.pucByte; - if( record->uxIncludeInAnswer == pdFALSE ) + if( record->uxServeRecord != dnsRECORD_SERVE_ANSWER ) { continue; } - /* Exclude from coverage because this should be impossible to fail. - * We already checked the name during question parsing. */ - if( !DNS_WriteName( ( char * ) pucInterimWrite, record->pcName ) ) /* LCOV_EXCL_BR_LINE */ + if( prvWriteDNSRecord( &xSet, record ) == pdFALSE ) { - /* LCOV_EXCL_START */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write record name" ) ); - /* This should not happen, since we have already calculated the required size. */ - break; - /* LCOV_EXCL_STOP */ + /* If writing this record failed, we skip it and move on to the next one. */ + continue; } - /* Interim write pointer that we will only "commit" once everything is done */ - pucInterimWrite += strlen( record->pcName ) + 2; - middle = ( MDNSResponseMiddle_t * ) pucInterimWrite; + uxNumAnswers++; + } - vSetField16( middle, MDNSResponseMiddle_t, usType, record->usRecordType ); - vSetField16( middle, MDNSResponseMiddle_t, usClass, dnsCLASS_IN ); /* 1: Class IN */ - vSetField32( middle, MDNSResponseMiddle_t, ulTTL, dnsLLMNR_TTL_VALUE ); + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) + { + DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; - switch( record->usRecordType ) /* LCOV_EXCL_BR_LINE The default case is not reached */ + if( record->uxServeRecord != dnsRECORD_SERVE_ADDITIONAL ) { - #if ( ipconfigUSE_IPv4 != 0 ) - case dnsTYPE_A_HOST: - { - MDNSResponseHostAEnd_t * host_end; - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, 4 ); - pucInterimWrite += sizeof( *middle ); - host_end = ( MDNSResponseHostAEnd_t * ) pucInterimWrite; - vSetField32( host_end, MDNSResponseHostAEnd_t, ipAddr, FreeRTOS_ntohl( pxNetworkBuffer->pxEndPoint->ipv4_settings.ulIPAddress ) ); - pucInterimWrite += sizeof( *host_end ); - break; - } - #endif /* ipconfigUSE_IPv4 */ - #if ( ipconfigUSE_IPv6 != 0 ) - case dnsTYPE_AAAA_HOST: - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, ipSIZE_OF_IPv6_ADDRESS ); - pucInterimWrite += sizeof( *middle ); - ( void ) memcpy( pucInterimWrite, pxNetworkBuffer->pxEndPoint->ipv6_settings.xIPAddress.ucBytes, ipSIZE_OF_IPv6_ADDRESS ); - pucInterimWrite += ipSIZE_OF_IPv6_ADDRESS; - break; - #endif /* ipconfigUSE_IPv6 */ - case dnsTYPE_PTR: - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcPtrRecord ) + 2 ); - pucInterimWrite += sizeof( *middle ); - - if( DNS_WriteName( ( char * ) pucInterimWrite, record->xData.pcPtrRecord ) == pdFALSE ) - { - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write PTR record" ) ); - /* Skip this record */ - continue; - } - - pucInterimWrite += strlen( record->xData.pcPtrRecord ) + 2; - break; - - case dnsTYPE_SRV: - { - MDNSResponseSRVEnd_t * srv_end; - vSetField16( - middle, - MDNSResponseMiddle_t, - usDataLength, - sizeof( MDNSResponseSRVEnd_t ) + strlen( record->xData.xSrvRecord.pcTarget ) + 2 ); - pucInterimWrite += sizeof( *middle ); - srv_end = ( MDNSResponseSRVEnd_t * ) pucInterimWrite; - vSetField16( srv_end, MDNSResponseSRVEnd_t, priority, 0 ); - vSetField16( srv_end, MDNSResponseSRVEnd_t, weight, 0 ); - vSetField16( srv_end, MDNSResponseSRVEnd_t, port, record->xData.xSrvRecord.usPort ); - pucInterimWrite += sizeof( *srv_end ); - - if( DNS_WriteName( ( char * ) pucInterimWrite, record->xData.xSrvRecord.pcTarget ) == pdFALSE ) - { - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write SRV record target name" ) ); - /* Skip this record */ - continue; - } - - pucInterimWrite += strlen( record->xData.xSrvRecord.pcTarget ) + 2; - break; - } - - case dnsTYPE_TXT: - { - size_t xTextLength = strlen( record->xData.pcTxtRecord ); - - if( xTextLength > 255 ) - { - /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write TXT record" ) ); - continue; - } - - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( record->xData.pcTxtRecord ) + 1 ); - pucInterimWrite += sizeof( *middle ); - *pucInterimWrite++ = ( uint8_t ) xTextLength; - memcpy( pucInterimWrite, record->xData.pcTxtRecord, xTextLength ); - pucInterimWrite += xTextLength; - break; - } + continue; + } - /* This region is unreachable in practice, because we already filtered the records in the question - * parsing */ - /* LCOV_EXCL_START */ - default: - FreeRTOS_printf( ( "DNS_ParseDNSReply: Unsupported record type %u\n", record->usRecordType ) ); - continue; - /* LCOV_EXCL_STOP */ + if( prvWriteDNSRecord( &xSet, record ) == pdFALSE ) + { + /* If writing this record failed, we skip it and move on to the next one. */ + continue; } - xSet.pucByte = pucInterimWrite; - uxNumAnswers++; + uxNumAdditionalRRs++; } if( uxNumAnswers == 0U ) @@ -1175,6 +1235,7 @@ } vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAnswers, uxNumAnswers ); + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, uxNumAdditionalRRs ); if( xSet.usPortNumber == ipLLMNR_PORT ) { @@ -1194,12 +1255,12 @@ if( uxIsLLMNR == pdFALSE ) { /* In MDNS, we need to remove the questions section */ - uint8_t * const start_of_questions = ( uint8_t * ) ( xSet.pxDNSMessageHeader ) + sizeof( DNSMessage_t ); + uint8_t * const pucStartOfQuestions = ( uint8_t * ) ( xSet.pxDNSMessageHeader ) + sizeof( DNSMessage_t ); - UBaseType_t const size_of_questions = ( UBaseType_t ) ( start_of_dns_answers - start_of_questions ); + UBaseType_t const size_of_questions = ( UBaseType_t ) ( pucStartOfAnswers - pucStartOfQuestions ); - UBaseType_t const size_of_answers = ( UBaseType_t ) ( xSet.pucByte - start_of_dns_answers ); - memmove( start_of_questions, start_of_dns_answers, size_of_answers ); + UBaseType_t const size_of_answers = ( UBaseType_t ) ( xSet.pucByte - pucStartOfAnswers ); + memmove( pucStartOfQuestions, pucStartOfAnswers, size_of_answers ); xSet.pucByte -= size_of_questions; vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usQuestions, 0 ); } diff --git a/source/include/FreeRTOS_DNS_Globals.h b/source/include/FreeRTOS_DNS_Globals.h index 3ed8e7fc88..e6ba85d9ca 100644 --- a/source/include/FreeRTOS_DNS_Globals.h +++ b/source/include/FreeRTOS_DNS_Globals.h @@ -126,11 +126,25 @@ #if ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) +/* Do not serve this record */ + #define dnsRECORD_SERVE_NO 0U + +/* Serve this record as an additional RR + * Writing `.uxServeRecord |= dnsRECORD_SERVE_ADDITIONAL` will cause the + * record to be served as an additional RR only if it was previously not + * being served, but will leave answers unchanged. + */ + #define dnsRECORD_SERVE_ADDITIONAL 1U + /* Serve this record as an answer */ + #define dnsRECORD_SERVE_ANSWER 3U + typedef struct xDNSRecord { uint16_t usRecordType; - /* Used by the backend to determine which fields to report */ - BaseType_t uxIncludeInAnswer; + + /* How to serve this record. + * See `dnsRECORD_SERVE_NO`, `dnsRECORD_SERVE_ADDITIONAL` and `dnsRECORD_SERVE_ANSWER`. */ + BaseType_t uxServeRecord; const char * pcName; union { @@ -162,6 +176,7 @@ #define xApplicationNBNSQueryHook_Multi xApplicationDNSQueryHook_Multi #endif #else /* if ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) */ + extern void xApplicationDNSRecordsMatchedHook( void ); #if ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) extern DNSRecord_t * xApplicationDNSRecordQueryHook( UBaseType_t * outLen ); extern BaseType_t xApplicationNBNSQueryHook( const char * pcName ); diff --git a/test/build-combination/Common/main.c b/test/build-combination/Common/main.c index ec9db0c332..11089465b4 100644 --- a/test/build-combination/Common/main.c +++ b/test/build-combination/Common/main.c @@ -213,6 +213,10 @@ int main( void ) return xReturn; } + void xApplicationDNSRecordsMatchedHook( void ) + { + } + #endif /* if ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_NBNS != 0 ) */ /*-----------------------------------------------------------*/ diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index 34160a8bdc..15b74869c9 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -73,6 +73,7 @@ static BaseType_t hook_called = pdFALSE; static char prvDNSRecordNames[ 16 ][ 256 ]; static DNSRecord_t prvDNSRecords[ 16 ]; static UBaseType_t prvDNSRecordsLen = 0; +static size_t prvDNSAdditionalRecordIndex = SIZE_MAX; /* =========================== STATIC FUNCTIONS =========================== */ static void dns_callback( const char * pcName, @@ -99,6 +100,7 @@ void setUp( void ) xBufferAllocFixedSize = pdFALSE; callback_called = 0; prvDNSRecordsLen = 0; + prvDNSAdditionalRecordIndex = SIZE_MAX; } /** @@ -132,6 +134,14 @@ DNSRecord_t * xApplicationDNSRecordQueryHook_Multi( struct xNetworkEndPoint * px return prvDNSRecords; } +void xApplicationDNSRecordsMatchedHook( void ) +{ + if( prvDNSAdditionalRecordIndex != SIZE_MAX ) + { + prvDNSRecords[ prvDNSAdditionalRecordIndex ].uxServeRecord |= dnsRECORD_SERVE_ADDITIONAL; + } +} + static void SetDNSRecordsSimple( char const * pcName ) { strncpy( prvDNSRecordNames[ 0 ], pcName, sizeof( prvDNSRecordNames[ 0 ] ) - 1 ); @@ -148,6 +158,11 @@ static void SetDNSRecordsSimple( char const * pcName ) prvDNSRecordsLen = 2; } +static void SetDNSRecordServeAdditional( size_t n ) +{ + prvDNSAdditionalRecordIndex = n; +} + /* ============================= TEST CASES =============================== */ /** @@ -3509,6 +3524,70 @@ void test_DNS_ParseDNSReply_questions_ptr_record( void ) expect_dns_result( &test_data, pdTRUE, dnsTYPE_PTR ); } +void test_DNS_ParseDNSReply_questions_additional_record( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_A_HOST, + }; + prvDNSRecords[ 1 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS_Additional", + .usRecordType = dnsTYPE_PTR, + .xData.pcPtrRecord = "TCP" + }; + prvDNSRecordsLen = 2; + + SetDNSRecordServeAdditional( 1 ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + +void test_DNS_ParseDNSReply_questions_additional_record_fail_to_serve( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_A_HOST, + }; + + /* This record should not serve because it is too long, + * but the first record should still be served */ + prvDNSRecords[ 1 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS_Additional", + .usRecordType = dnsTYPE_TXT, + .xData.pcTxtRecord = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + }; + prvDNSRecordsLen = 2; + + SetDNSRecordServeAdditional( 1 ); + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + A_TYPE_IN_CLASS( test_data.write_head ); + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_A_HOST ); +} + + void test_DNS_ParseDNSReply_questions_ptr_record_matches_any( void ) { DnsQTestData_t test_data = dns_q_test_init( 1 ); From 6b7700f4c662e5bfe4f25311642e78f999300bae Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Wed, 6 May 2026 10:08:08 +0200 Subject: [PATCH 08/10] Support multiple strings in TXT records --- source/FreeRTOS_DNS_Parser.c | 44 ++++++--- source/include/FreeRTOS_DNS_Globals.h | 8 +- .../FreeRTOS_DNS_Parser_utest.c | 92 ++++++++++++++----- 3 files changed, 105 insertions(+), 39 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 35aebe231b..8f5468fcde 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -637,20 +637,30 @@ case dnsTYPE_TXT: { - size_t xTextLength = strlen( pxRecord->xData.pcTxtRecord ); + UBaseType_t i; + uint16_t usTotalLength = 0; + pucWriteHead += sizeof( *middle ); - if( xTextLength > 255 ) + for( i = 0; i < pxRecord->xData.xTxtRecord.uxStringCount; i++ ) { - /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write TXT record" ) ); - return pdFALSE; + char const * const pcString = pxRecord->xData.xTxtRecord.ppcStrings[ i ]; + size_t const xTextLength = strlen( pcString ); + + if( xTextLength > 255 ) + { + /* Each TXT record must be less than 256 bytes, since the length is stored in a single byte. */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to write TXT record" ) ); + return pdFALSE; + } + + *pucWriteHead++ = ( uint8_t ) xTextLength; + memcpy( pucWriteHead, pcString, xTextLength ); + pucWriteHead += xTextLength; + /* We've already checked total length so this cast should be safe */ + usTotalLength += ( uint16_t ) ( xTextLength + 1 ); } - vSetField16( middle, MDNSResponseMiddle_t, usDataLength, strlen( pxRecord->xData.pcTxtRecord ) + 1 ); - pucWriteHead += sizeof( *middle ); - *pucWriteHead++ = ( uint8_t ) xTextLength; - memcpy( pucWriteHead, pxRecord->xData.pcTxtRecord, xTextLength ); - pucWriteHead += xTextLength; + vSetField16( middle, MDNSResponseMiddle_t, usDataLength, usTotalLength ); break; } @@ -1104,10 +1114,16 @@ case dnsTYPE_TXT: { - size_t const uxTextLength = strlen( pRecord->xData.pcTxtRecord ); - /* TXT records don't use length-label strings, so it's not +2. */ - /* Just a length field and no null terminator. So it's +1. */ - uxExtraSize += uxTextLength + 1; /* Text. */ + UBaseType_t i; + + for( i = 0; i < pRecord->xData.xTxtRecord.uxStringCount; i++ ) + { + size_t const uxTextLength = strlen( pRecord->xData.xTxtRecord.ppcStrings[ i ] ); + /* TXT records don't use length-label strings, so it's not +2. */ + /* Just a length field and no null terminator. So it's +1. */ + uxExtraSize += uxTextLength + 1; /* Text. */ + } + break; } diff --git a/source/include/FreeRTOS_DNS_Globals.h b/source/include/FreeRTOS_DNS_Globals.h index e6ba85d9ca..2585371f2e 100644 --- a/source/include/FreeRTOS_DNS_Globals.h +++ b/source/include/FreeRTOS_DNS_Globals.h @@ -148,13 +148,17 @@ const char * pcName; union { - char * pcPtrRecord; + char const * pcPtrRecord; struct { const char * pcTarget; uint16_t usPort; } xSrvRecord; - char * pcTxtRecord; + struct + { + const char * const * ppcStrings; + UBaseType_t uxStringCount; + } xTxtRecord; } xData; } DNSRecord_t; diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index 15b74869c9..d321b67ed1 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -3447,11 +3447,40 @@ void test_DNS_ParseDNSReply_questions_srv_record( void ) void test_DNS_ParseDNSReply_questions_txt_record( void ) { DnsQTestData_t test_data = dns_q_test_init( 1 ); + char const * const txtRecord[] = { "Some Text" }; prvDNSRecords[ 0 ] = ( DNSRecord_t ) { .pcName = "FreeRTOS", .usRecordType = dnsTYPE_TXT, - .xData.pcTxtRecord = "Some Text" + .xData.xTxtRecord = + { + .ppcStrings = txtRecord, + .uxStringCount = 1, + } + }; + prvDNSRecordsLen = 1; + + PUSH_LABEL( test_data.write_head, "FreeRTOS" ); + END_LABELS( test_data.write_head ); + memcpy( test_data.write_head, "\x00\x21\x00\x01", 4 ); /* SRV record, IN class */ + test_data.write_head += 4; + + expect_dns_result( &test_data, pdTRUE, dnsTYPE_TXT ); +} + +void test_DNS_ParseDNSReply_questions_txt_record_multiple_strings( void ) +{ + DnsQTestData_t test_data = dns_q_test_init( 1 ); + char const * const txtRecord[] = { "Some Text", "Some more Text" }; + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_TXT, + .xData.xTxtRecord = + { + .ppcStrings = txtRecord, + .uxStringCount = 2, + } }; prvDNSRecordsLen = 1; @@ -3467,20 +3496,28 @@ void test_DNS_ParseDNSReply_questions_txt_record_too_long( void ) { DnsQTestData_t test_data = dns_q_test_init( 1 ); + char const * const txtRecord[] = + { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + }; + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { .pcName = "FreeRTOS", .usRecordType = dnsTYPE_TXT, - .xData.pcTxtRecord = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" + .xData.xTxtRecord = + { + .ppcStrings = txtRecord, + .uxStringCount = 1 + } }; prvDNSRecordsLen = 1; @@ -3558,22 +3595,31 @@ void test_DNS_ParseDNSReply_questions_additional_record_fail_to_serve( void ) .usRecordType = dnsTYPE_A_HOST, }; + char const * const txtRecord[] = + { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + }; + + /* This record should not serve because it is too long, * but the first record should still be served */ prvDNSRecords[ 1 ] = ( DNSRecord_t ) { .pcName = "FreeRTOS_Additional", .usRecordType = dnsTYPE_TXT, - .xData.pcTxtRecord = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" + .xData.xTxtRecord = + { + .ppcStrings = txtRecord, + .uxStringCount = 1 + } }; prvDNSRecordsLen = 2; From 5ec0cc05f3c80066bbc562e820cf57a7bce18d32 Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Wed, 6 May 2026 13:51:28 +0200 Subject: [PATCH 09/10] Update DNS_SkipNameField to correctly parse records with pointers mid-name --- source/FreeRTOS_DNS_Parser.c | 75 +++++++++---------- .../FreeRTOS_DNS_Parser_utest.c | 57 ++++++++++++-- 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index 8f5468fcde..dd7589d1c9 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -174,65 +174,58 @@ size_t DNS_SkipNameField( const uint8_t * pucByte, size_t uxLength ) { - size_t uxChunkLength; - size_t uxSourceLenCpy = uxLength; size_t uxIndex = 0U; + uint8_t ucLength; - if( uxSourceLenCpy == 0U ) + if( uxLength == 0U ) { - uxIndex = 0U; + return 0U; } - /* Determine if the name is the fully coded name, or an offset to the name - * elsewhere in the message. */ - else if( ( pucByte[ uxIndex ] & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) + if( pucByte[ 0 ] == 0U ) { - /* Jump over the two byte offset. */ - if( uxSourceLenCpy > sizeof( uint16_t ) ) - { - uxIndex += sizeof( uint16_t ); - } - else - { - uxIndex = 0U; - } + return 0U; } - else + + /* Walk over labels until the terminating zero-length label, or a compression + * pointer. Per RFC 1035, a compression pointer always terminates the current + * name and consumes two bytes. */ + while( uxIndex < uxLength ) { - /* pucByte points to the full name. Walk over the string. */ - while( ( pucByte[ uxIndex ] != 0U ) && ( uxSourceLenCpy > 1U ) ) + ucLength = pucByte[ uxIndex ]; + + if( ucLength == 0U ) { - /* Conversion to size_t causes addition to be done - * in size_t */ - uxChunkLength = ( ( size_t ) pucByte[ uxIndex ] ) + 1U; + uxIndex++; + return uxIndex; + } - if( uxSourceLenCpy > uxChunkLength ) - { - uxSourceLenCpy -= uxChunkLength; - uxIndex += uxChunkLength; - } - else + if( ( ucLength & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) + { + if( ( uxLength - uxIndex ) < sizeof( uint16_t ) ) { - uxIndex = 0U; - break; + return 0U; } + + uxIndex += sizeof( uint16_t ); + return uxIndex; } - /* Confirm that a fully formed name was found. */ - if( uxIndex > 0U ) + if( ucLength > 63U ) { - if( pucByte[ uxIndex ] == 0U ) - { - uxIndex++; - } - else - { - uxIndex = 0U; - } + return 0U; } + + /* Check that this full label fits within the provided length. */ + if( ( uxLength - uxIndex ) <= ( size_t ) ucLength ) + { + return 0U; + } + + uxIndex += ( size_t ) ucLength + 1U; } - return uxIndex; + return 0U; } diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index d321b67ed1..62ce1074d2 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -456,13 +456,13 @@ void test_DNS_SkipNameField_failed_() */ void test_DNS_SkipNameField_fail_fully_coded( void ) { - uint8_t pucByte[ 2 ] = { 0 }; + uint8_t pucByte[ 1 ] = { 0 }; size_t ret; - memset( pucByte, 0x00, 2 ); + memset( pucByte, 0x00, 1 ); pucByte[ 0 ] = dnsNAME_IS_OFFSET; - ret = DNS_SkipNameField( pucByte, 2 ); + ret = DNS_SkipNameField( pucByte, 1 ); TEST_ASSERT_EQUAL( 0, ret ); } @@ -524,6 +524,51 @@ void test_DNS_SkipNameField_small_buffer( void ) TEST_ASSERT_EQUAL( 0, ret ); } +/** + * @brief ensures that when the last name field is less than the buffer + * remaining size, zero is returned + */ +void test_DNS_SkipNameField_fail_no_terminator( void ) +{ + uint8_t pucByte[ 300 ] = { 0 }; + + memset( pucByte, 0x00, 300 ); + pucByte[ 0 ] = 8; + strcpy( pucByte + 1, "FreeRTOS" ); + pucByte[ 9 ] = 7; + strcpy( pucByte + 10, "PlusTCP" ); + size_t ret; + + /* 16 is enough for *FreeRTOS*PlusTCP + * but not the final zero length label */ + ret = DNS_SkipNameField( pucByte, 17 ); + + TEST_ASSERT_EQUAL( 0, ret ); +} + +/** + * @brief ensures that labels greater than 63 length are rejected + */ +void test_DNS_SkipNameField_fail_too_long( void ) +{ + uint8_t pucByte[ 66 ] = { 0 }; + + memset( pucByte, 0x00, 66 ); + pucByte[ 0 ] = 64; + strcpy( pucByte + 1, + "0123456789" + "0123456789" + "0123456789" + "0123456789" + "0123456789" + "0123456789" + "0123" ); + size_t ret; + ret = DNS_SkipNameField( pucByte, 65 ); + + TEST_ASSERT_EQUAL( 0, ret ); +} + /* =================== test prepare Reply DNS Message ======================= */ /** @@ -1789,6 +1834,7 @@ void test_DNS_ParseDNSReply_answer_record_too_many_answers( void ) beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); beg += 38; + beg++; /* Null terminate */ beg += sizeof( uint32_t ); @@ -1797,11 +1843,6 @@ void test_DNS_ParseDNSReply_answer_record_too_many_answers( void ) strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); beg += 38; - pucUDPPayloadBuffer[ beg ] = 38; - beg++; - strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); - beg += 38; - usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usType */ usChar2u16_ExpectAnyArgsAndReturn( dnsNBNS_FLAGS_OPCODE_QUERY ); /* usClass */ From cf405a04423000b823990329b102e397a391b14b Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Wed, 6 May 2026 16:02:42 +0200 Subject: [PATCH 10/10] Improve accuracy of DNS output buffer calculation --- source/FreeRTOS_DNS_Parser.c | 24 ++++++++++-- .../FreeRTOS_DNS_Parser_utest.c | 37 ++++++++++++++++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index dd7589d1c9..85d6fd74e7 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -1069,10 +1069,26 @@ uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); /* LCOV_EXCL_BR_LINE */ - uxDataLength = uxBufferLength + - sizeof( UDPHeader_t ) + - sizeof( EthernetHeader_t ) + - uxIPHeaderSizePacket( pxNetworkBuffer ); + /* Base the outgoing packet size on only the DNS payload we actually + * retain, not the full incoming buffer. Any incoming known-answer + * records after the questions section are discarded and overwritten. + * For mDNS the questions section is also stripped (RFC 6762-6), so + * only the fixed-size header is kept. */ + { + uint8_t * const pucStartOfQuestions = ( uint8_t * ) ( xSet.pxDNSMessageHeader ) + sizeof( DNSMessage_t ); + UBaseType_t const uxSizeOfQuestions = ( UBaseType_t ) ( xSet.pucByte - pucStartOfQuestions ); + UBaseType_t uxRetainedDNSPayload = sizeof( DNSMessage_t ); + + if( xSet.usPortNumber == ipLLMNR_PORT ) + { + uxRetainedDNSPayload += uxSizeOfQuestions; + } + + uxDataLength = uxRetainedDNSPayload + + sizeof( UDPHeader_t ) + + sizeof( EthernetHeader_t ) + + uxIPHeaderSizePacket( pxNetworkBuffer ); + } /* Calculate how big our response is going to end up being. */ for( i = 0; i < xSet.uxDNSRecordCount; i++ ) diff --git a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c index 62ce1074d2..7a7dd57544 100644 --- a/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c +++ b/test/unit-test/FreeRTOS_DNS_Parser/FreeRTOS_DNS_Parser_utest.c @@ -3813,26 +3813,53 @@ void test_DNS_ParseDNSReply_questions_missing_endpoint( void ) void test_DNS_ParseDNSReply_questions_fixed_size_not_big_enough( void ) { + /* Create a single response to a text question with a really big + * string that will overflow the MTU */ DnsQTestData_t test_data = dns_q_test_init( 1 ); - SetDNSRecordsSimple( "FreeRTOS" ); + char really_long_string[ 255 ]; + + memset( really_long_string, 'A', 254 ); + really_long_string[ 254 ] = 0; + + char const * const long_strings[ 6 ] = + { + really_long_string, + really_long_string, + really_long_string, + really_long_string, + really_long_string, + really_long_string, + }; + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .usRecordType = dnsTYPE_TXT, + .pcName = "FreeRTOS", + .xData.xTxtRecord = + { + .ppcStrings = long_strings, + .uxStringCount = 6 + } + }; + + prvDNSRecordsLen = 1; PUSH_LABEL( test_data.write_head, "FreeRTOS" ); END_LABELS( test_data.write_head ); - A_TYPE_IN_CLASS( test_data.write_head ); + + memcpy( test_data.write_head, "\x00\x10\00\01", 4 ); test_data.write_head += 4; xBufferAllocFixedSize = true; UBaseType_t i; - usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_A_HOST ); /* usType */ - usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ + usChar2u16_ExpectAnyArgsAndReturn( dnsTYPE_TXT ); /* usType */ + usChar2u16_ExpectAnyArgsAndReturn( dnsCLASS_IN ); /* usClass */ pxUDPPayloadBuffer_to_NetworkBuffer_ExpectAnyArgsAndReturn( test_data.pxNetworkBuffer ); uxIPHeaderSizePacket_IgnoreAndReturn( ipSIZE_OF_IPv4_HEADER ); BaseType_t ret = DNS_ParseDNSReply( test_data.pucUDPPayloadBuffer, - ipconfigNETWORK_MTU, + test_data.write_head - test_data.pucUDPPayloadBuffer, &test_data.pxAddressInfo, pdFALSE, test_data.usPort );