diff --git a/source/FreeRTOS_DNS_Parser.c b/source/FreeRTOS_DNS_Parser.c index fac3cc466..7ead0e24c 100644 --- a/source/FreeRTOS_DNS_Parser.c +++ b/source/FreeRTOS_DNS_Parser.c @@ -174,69 +174,729 @@ 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 ) ) + return 0U; + } + + /* 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 ) + { + ucLength = pucByte[ uxIndex ]; + + if( ucLength == 0U ) + { + uxIndex++; + return uxIndex; + } + + if( ( ucLength & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) { + if( ( uxLength - uxIndex ) < sizeof( uint16_t ) ) + { + return 0U; + } + uxIndex += sizeof( uint16_t ); + return uxIndex; } - else + + if( ucLength > 63U ) { - 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; } - else + + return 0U; + } + + + #if ( ( ipconfigUSE_LLMNR != 0 ) || ( ipconfigUSE_MDNS != 0 ) ) + +/** + * @brief Check two strings for equality, up to the given length, with + * ascii case-insensitivity. + */ + static BaseType_t prvCompareStringsCaseInsensitive( const char * s1, + const char * s2, + size_t n ) { - /* pucByte points to the full name. Walk over the string. */ - while( ( pucByte[ uxIndex ] != 0U ) && ( uxSourceLenCpy > 1U ) ) + while( n-- != 0 ) { - /* Conversion to size_t causes addition to be done - * in size_t */ - uxChunkLength = ( ( size_t ) pucByte[ uxIndex ] ) + 1U; + char c1 = *s1++; + char c2 = *s2++; - if( uxSourceLenCpy > uxChunkLength ) + if( ( c1 >= 'A' ) && ( c1 <= 'Z' ) ) { - uxSourceLenCpy -= uxChunkLength; - uxIndex += uxChunkLength; + c1 += 'a' - 'A'; } - else + + if( ( c2 >= 'A' ) && ( c2 <= 'Z' ) ) { - uxIndex = 0U; - break; + c2 += 'a' - 'A'; + } + + if( c1 > c2 ) + { + return pdFALSE; + } + + if( c2 > c1 ) + { + return pdFALSE; } } - /* Confirm that a fully formed name was found. */ - if( uxIndex > 0U ) + return pdTRUE; + } + +/** + * @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. + * 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. + * @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, + uint8_t const * pcDNSMessage, + uint8_t const * pcDNSMessageEnd, + char const * pcDotString ) + { + uint8_t const * pcDnsSegment = pcDNSString; + char const * pcDotSegment = pcDotString; + BaseType_t uxAlreadyFollowedPointer = pdFALSE; + + configASSERT( pcDNSString >= pcDNSMessage ); /* LCOV_EXCL_BR_LINE */ + configASSERT( pcDNSString < pcDNSMessageEnd ); /* LCOV_EXCL_BR_LINE */ + + for( ; ; ) { - if( pucByte[ uxIndex ] == 0U ) + UBaseType_t const uxSegmentLength = ( UBaseType_t ) ( *pcDnsSegment ); + BaseType_t ulComparison; + + if( uxSegmentLength == 0 ) { - uxIndex++; + /* If we have reached the end of the DNS string, we should also be at the end of + * the dotted string. */ + return *pcDotSegment == '\0'; } - else + + if( ( uxSegmentLength & dnsNAME_IS_OFFSET ) == dnsNAME_IS_OFFSET ) { - uxIndex = 0U; + /* This is a compressed label pointing to previous data in the DNS body */ + + uint16_t usLocation; + + if( uxAlreadyFollowedPointer == pdTRUE ) + { + /* 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; + uxAlreadyFollowedPointer = pdTRUE; + continue; + } + + if( uxSegmentLength > 63 ) + { + /* Each segment in the DNS string must be less than 64 characters. */ + return pdFALSE; + } + + 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 = prvCompareStringsCaseInsensitive( ( char const * ) pcDnsSegment, pcDotSegment, uxSegmentLength ); + + if( ulComparison == pdFALSE ) + { + return pdFALSE; + } + + pcDnsSegment += uxSegmentLength; + pcDotSegment += uxSegmentLength; + + /* pcDotSegment now points to '.' or '\0' (verified above). */ + + if( *pcDotSegment == '.' ) + { + /* Move past the dot in the dotted string, and continue with the next segment. */ + pcDotSegment++; } } } - return uxIndex; + +/** + * @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; + } + } + } + +/** + * @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 ) + { + #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 + UBaseType_t x; + 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 ].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: + { + UBaseType_t i; + uint16_t usTotalLength = 0; + pucWriteHead += sizeof( *middle ); + + for( i = 0; i < pxRecord->xData.xTxtRecord.uxStringCount; i++ ) + { + 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, usTotalLength ); + 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 ) ) */ + + +/** + * @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. + * @return pdTRUE if everything went okay + */ + static BaseType_t parseDNSQuestions( ParseSet_t * xSet ) + { + UBaseType_t x; + size_t uxResult; + + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + NetworkEndPoint_t xEndPoint; + const uint8_t * pucThisNameField; + BaseType_t xEndPointValid = pdFALSE; + #endif + + for( x = 0U; x < xSet->usQuestions; x++ ) + { + #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) + 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 ) ); + } + 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( DNS_GetRecords( xSet ) == pdFALSE ) + { + return pdFALSE; + } + + #if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) + { + ( void ) i; + + if( x == 0U ) + { + BaseType_t xIsMatched; + + if( xEndPointValid == pdFALSE ) + { + if( DNS_GetEndpoint( xSet, &xEndPoint ) == pdFALSE ) + { + return pdFALSE; + } + + xEndPointValid = pdTRUE; + } + + #if ( ( ipconfigIPv4_BACKWARD_COMPATIBLE == 1 ) ) + xIsMatched = xApplicationDNSQueryHook( xSet->pcName ); + #else + xIsMatched = xApplicationDNSQueryHook_Multi( &xEndPoint, xSet->pcName ); + #endif + + if( xIsMatched == pdTRUE ) + { + xSet->xDNSRecordsMatched = pdTRUE; + #if ( ( ipconfigUSE_IPv6 != 0 ) ) + if( + ( xEndPoint.bits.bIPv6 != pdFALSE ) && + ( ( xSet->usType == dnsTYPE_AAAA_HOST ) || + ( xSet->usType == dnsTYPE_ANY_HOST ) ) ) + { + xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = + ( DNSRecord_t ) { + .usRecordType = dnsTYPE_AAAA_HOST, + .pcName = xSet->pcName, + .uxServeRecord = dnsRECORD_SERVE_ANSWER, + }; + } + #endif /* if ( ( ipconfigUSE_IPv6 != 0 ) ) */ + #if ( ( ipconfigUSE_IPv4 != 0 ) ) + if( + ( xEndPoint.ipv4_settings.ulIPAddress != 0U ) && + ( ( xSet->usType == dnsTYPE_A_HOST ) || + ( xSet->usType == dnsTYPE_ANY_HOST ) ) ) + { + xSet->pxDNSRecords[ xSet->uxDNSRecordCount++ ] = + ( DNSRecord_t ) { + .usRecordType = dnsTYPE_A_HOST, + .pcName = xSet->pcName, + .uxServeRecord = dnsRECORD_SERVE_ANSWER, + }; + } + #endif /* if ( ( ipconfigUSE_IPv4 != 0 ) ) */ + } + } + } + #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( ( "parseDNSQuestions: Unsupported record type %u\n", pRecord->usRecordType ) ); + /* Unsupported record type. Skip. */ + continue; + } + + xTypeMatch = ( pRecord->usRecordType == xSet->usType ) || ( xSet->usType == dnsTYPE_ANY_HOST ); + xNameMatch = DNS_NameEqual( + pucThisNameField, + ( const uint8_t * ) xSet->pxDNSMessageHeader, + xSet->pucUDPPayloadBuffer + xSet->uxBufferLength, + pRecord->pcName ) == pdTRUE; + + if( ( xTypeMatch == pdTRUE ) && ( xNameMatch == pdTRUE ) ) + { + if( xEndPointValid == 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; /* LCOV_EXCL_LINE */ + } + + 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; + } + + 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->uxServeRecord = dnsRECORD_SERVE_ANSWER; + xSet->xDNSRecordsMatched = pdTRUE; + } + } + #endif /* if ( ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 1 ) ) */ + } + #endif /* ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) */ + + /* 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++ ) */ + + #if ( ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) && ( ipconfigDNSQuery_BACKWARD_COMPATIBLE == 0 ) ) + if( xSet->xDNSRecordsMatched ) + { + xApplicationDNSRecordsMatchedHook(); + } + #endif + + return pdTRUE; } + /** - * @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. @@ -259,7 +919,6 @@ uint16_t usPort ) { ParseSet_t xSet; - uint16_t x; BaseType_t xReturn = pdTRUE; uint32_t ulIPAddress = 0U; BaseType_t xDNSHookReturn = 0; @@ -296,9 +955,19 @@ /* 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 ) ) + #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; + xSet.uxDNSRecordCount = 0; + #endif + #endif /* Start at the first byte after the header. */ xSet.pucUDPPayloadBuffer = pucUDPPayloadBuffer; @@ -324,6 +993,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; @@ -333,8 +1007,8 @@ #if ( ipconfigUSE_DNS_CACHE == 1 ) || ( ipconfigDNS_USE_CALLBACKS == 1 ) uxResult = DNS_ReadNameField( &xSet, sizeof( xSet.pcName ) ); - ( void ) uxResult; #endif + ( void ) uxResult; } } else @@ -347,74 +1021,7 @@ } } - for( x = 0U; x < xSet.usQuestions; x++ ) - { - #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - { - if( x == 0U ) - { - 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; - - 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 ) ) - { - #if ( ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_MDNS == 1 ) ) - { - /* usChar2u16 returns value in host endianness. */ - xSet.usType = usChar2u16( xSet.pucByte ); - xSet.usClass = usChar2u16( &( xSet.pucByte[ 2 ] ) ); - } - #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++ ) */ + xReturn = parseDNSQuestions( &xSet ); if( xReturn == pdFALSE ) { @@ -425,232 +1032,246 @@ 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 ) ) - - /* 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( xSet.xDNSRecordsMatched == pdTRUE ) { + UBaseType_t i; + UBaseType_t uxExtraSize = 0; + UBaseType_t uxDataLength; + UBaseType_t uxNumAnswers = 0; + UBaseType_t uxNumAdditionalRRs = 0; + uint8_t * pucDNSPayloadBase = NULL; + BaseType_t uxLength; + UBaseType_t uxUDPOffset; NetworkBufferDescriptor_t * pxNetworkBuffer; - NetworkEndPoint_t * pxEndPoint, xEndPoint; - size_t uxUDPOffset; - - #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 ) - { - break; - } - #endif - pxNetworkBuffer = pxUDPPayloadBuffer_to_NetworkBuffer( pucUDPPayloadBuffer ); + /* 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(). */ - if( pxNetworkBuffer == NULL ) + if( pxNetworkBuffer == NULL ) /* LCOV_EXCL_BR_LINE */ { - /* _HT_ just while testing. When the program gets here, - * pucUDPPayloadBuffer was invalid. */ - FreeRTOS_printf( ( "DNS_ParseDNSReply: pucUDPPayloadBuffer was invalid\n" ) ); + /* LCOV_EXCL_START */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Failed to get network buffer\n" ) ); break; + /* LCOV_EXCL_STOP */ } - uxUDPOffset = ( size_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); - configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); + uxUDPOffset = ( UBaseType_t ) ( pucUDPPayloadBuffer - pxNetworkBuffer->pucEthernetBuffer ); + configASSERT( ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv4 ) || ( uxUDPOffset == ipUDP_PAYLOAD_OFFSET_IPv6 ) ); /* LCOV_EXCL_BR_LINE */ - if( pxNetworkBuffer->pxEndPoint == NULL ) + /* 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. */ { - break; - } - - pxEndPoint = pxNetworkBuffer->pxEndPoint; + 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 ); - /* Make a copy of the end-point because xApplicationDNSQueryHook() is allowed - * to write into it. */ - ( void ) memcpy( &( xEndPoint ), pxEndPoint, sizeof( xEndPoint ) ); + if( xSet.usPortNumber == ipLLMNR_PORT ) + { + uxRetainedDNSPayload += uxSizeOfQuestions; + } + else if( xSet.usPortNumber == ipMDNS_PORT ) + { + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usQuestions, 0 ); + } + else + { + /* Should not happen. Let's refuse to send our answer */ + FreeRTOS_printf( ( "DNS_ParseDNSReply: Unexpected port number %u\n", xSet.usPortNumber ) ); + break; + } - #if ( ipconfigUSE_IPv6 != 0 ) - { - /*logging*/ - FreeRTOS_printf( ( "DNS_ParseDNSReply[%s]: type %04X\n", xSet.pcName, xSet.usType ) ); + uxDataLength = uxRetainedDNSPayload + + sizeof( UDPHeader_t ) + + sizeof( EthernetHeader_t ) + + uxIPHeaderSizePacket( pxNetworkBuffer ); - xEndPoint.usDNSType = ( uint8_t ) xSet.usType; + /* Move write-head to where response records start in the + * retained DNS payload. */ + xSet.pucByte = &( pucUDPPayloadBuffer[ uxRetainedDNSPayload ] ); } - #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 - /* 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 ) ) + /* Calculate how big our response is going to end up being. */ + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) { - xDNSHookReturn = pdFALSE; - } + DNSRecord_t const * pRecord = &xSet.pxDNSRecords[ i ]; - if( xDNSHookReturn != pdFALSE ) - { - 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 ) + if( pRecord->uxServeRecord == dnsRECORD_SERVE_NO ) { - /* The number of bytes needed by Answers section (16 bytes). */ - uxExtraLength = sizeof( LLMNRAnswer_t ); + continue; } - #else /* ( ipconfigUSE_IPv4 != 0 ) */ + + switch( pRecord->usRecordType ) /* LCOV_EXCL_BR_LINE The default case is not reached */ { - /* do nothing, coverity happy */ + #if ( ipconfigUSE_IPv4 != 0 ) + case dnsTYPE_A_HOST: + uxExtraSize += 4; /* IPV4 address */ + + 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: + { + UBaseType_t j; + + for( j = 0; j < pRecord->xData.xTxtRecord.uxStringCount; j++ ) + { + size_t const uxTextLength = strlen( pRecord->xData.xTxtRecord.ppcStrings[ j ] ); + /* 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 ) ); + continue; + /* LCOV_EXCL_STOP */ } - #endif /* ( ipconfigUSE_IPv4 != 0 ) */ - if( xBufferAllocFixedSize == pdFALSE ) + /* Common elements */ + uxExtraSize += strlen( pRecord->pcName ) + 2; /* Name */ + uxExtraSize += 2; /* Type */ + uxExtraSize += 2; /* Class */ + uxExtraSize += 4; /* TTL */ + uxExtraSize += 2; /* RDLENGTH */ + } + + if( xBufferAllocFixedSize == pdFALSE ) + { + /* Set the size of the outgoing packet. */ + pxNetworkBuffer->xDataLength = uxDataLength; + pxNewBuffer = pxDuplicateNetworkBufferWithDescriptor( pxNetworkBuffer, + uxDataLength + + uxExtraSize ); + + if( pxNewBuffer != NULL ) { - /* 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; + BaseType_t xOffset1, xOffset2; - xOffset1 = ( BaseType_t ) ( xSet.pucByte - pucUDPPayloadBuffer ); - xOffset2 = ( BaseType_t ) ( ( ( uint8_t * ) xSet.pcRequestedName ) - pucUDPPayloadBuffer ); + xOffset1 = ( BaseType_t ) ( xSet.pucByte - pucUDPPayloadBuffer ); + xOffset2 = ( BaseType_t ) ( ( ( uint8_t * ) xSet.pcRequestedName ) - pucUDPPayloadBuffer ); - pxNetworkBuffer = pxNewBuffer; - pucNewBuffer = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); + pxNetworkBuffer = pxNewBuffer; + pucDNSPayloadBase = &( pxNetworkBuffer->pucEthernetBuffer[ uxUDPOffset ] ); - 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; - } + xSet.pucByte = &( pucDNSPayloadBase[ xOffset1 ] ); + xSet.pcRequestedName = ( char * ) &( pucDNSPayloadBase[ xOffset2 ] ); + xSet.pxDNSMessageHeader = ( ( DNSMessage_t * ) pucDNSPayloadBase ); } else { - /* 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; - } + break; } - - if( ( pxNetworkBuffer != NULL ) ) + } + else + { + /* When xBufferAllocFixedSize is TRUE, check if the buffer size is big enough. + * We reuse the same buffer (no reallocation), so no pointer remapping needed. */ + if( ( uxDataLength + uxExtraSize ) <= ipconfigNETWORK_MTU + ipSIZE_OF_ETH_HEADER ) { - 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 ); - } + pucDNSPayloadBase = pucUDPPayloadBuffer; + } + else + { + break; + } + } - 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 */ + /* We leave 'usIdentifier' and 'usQuestions' untouched */ + vSetField16( + xSet.pxDNSMessageHeader, + DNSMessage_t, + usFlags, + 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 */ + /* Number of additional records will be filled later */ + + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) + { + DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; - if( xSet.usQuestions > 0 ) - { - pxAnswer->ucNameCode = dnsNAME_IS_OFFSET; - pxAnswer->ucNameOffset = ( uint8_t ) ( xSet.pcRequestedName - ( char * ) pucNewBuffer ); - } + if( record->uxServeRecord != dnsRECORD_SERVE_ANSWER ) + { + continue; + } - 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 ); + if( prvWriteDNSRecord( &xSet, record ) == pdFALSE ) + { + /* If writing this record failed, we skip it and move on to the next one. */ + continue; + } - uxDistance = ( size_t ) ( ( ( const uint8_t * ) pxAnswer ) - pucNewBuffer ); + uxNumAnswers++; + } - #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 ); - } + for( i = 0; i < xSet.uxDNSRecordCount; i++ ) + { + DNSRecord_t const * record = &xSet.pxDNSRecords[ i ]; - prepareReplyDNSMessage( pxNetworkBuffer, usLength ); + if( record->uxServeRecord != dnsRECORD_SERVE_ADDITIONAL ) + { + continue; + } - /* This function will fill in the eth addresses and send the packet. - * xReleaseAfterSend = pdFALSE. */ - vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); + if( prvWriteDNSRecord( &xSet, record ) == pdFALSE ) + { + /* If writing this record failed, we skip it and move on to the next one. */ + continue; } + + uxNumAdditionalRRs++; } - else + + if( uxNumAnswers == 0U ) { - /* Not an expected reply. */ + /* 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 ); + vSetField16( xSet.pxDNSMessageHeader, DNSMessage_t, usAdditionalRRs, uxNumAdditionalRRs ); + + uxLength = ( BaseType_t ) ( xSet.pucByte - pucDNSPayloadBase ); + prepareReplyDNSMessage( pxNetworkBuffer, uxLength ); + /* This function will fill in the eth addresses and send the packet */ + vReturnEthernetFrame( pxNetworkBuffer, pdFALSE ); } - #endif /* ipconfigUSE_LLMNR == 1 */ - ( void ) uxBytesRead; + #endif /* ipconfigUSE_LLMNR == 1 || ipconfigUSE_MDNS == 1 */ } while( ipFALSE_BOOL ); - /* coverity[deadcode] */ if( pxNewBuffer != NULL ) { vReleaseNetworkBufferAndDescriptor( pxNewBuffer ); @@ -1243,9 +1864,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/FreeRTOSIPConfigDefaults.h b/source/include/FreeRTOSIPConfigDefaults.h index 2b2bfbe17..c4ba7810c 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 a47013b12..bf774e771 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 ) @@ -68,8 +70,11 @@ #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 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 @@ -118,6 +123,128 @@ #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 ) + +/* + * MDNS record serving can be used to implement DNS-SD. A minimal example: + * static char const* const TxtRecordStrings[] = { + * "name=FreeRTOS", + * "example=DNS-SD", + * }; + * + * static DNSRecord_t DnsRecords[] = { + * {.usRecordType = dnsTYPE_PTR, + * .pcName = "_services._dns-sd._udp.local", + * .xData.pcPtrRecord = "_myservice._udp.local"}, + * + * {.usRecordType = dnsTYPE_PTR, + * .pcName = "_myservice._udp.local", + * .xData.pcPtrRecord = "instance._myservice._udp.local"}, + * + * {.usRecordType = dnsTYPE_SRV, + * .pcName = "instance._myservice._udp.local", + * .xData.xSrvRecord = {.pcTarget = "myhostname.local", .usPort = 123}}, + * + * {.usRecordType = dnsTYPE_TXT, + * .pcName = "instance._myservice._udp.local", + * .xData.xTxtRecord = + * { + * .ppcStrings = TxtRecordStrings, + * .uxStringCount = 2, + * }}, + * + * {.usRecordType = dnsTYPE_A_HOST, .pcName = "myhostname.local"},e + * }; + * + * DNSRecord_t* xApplicationDNSRecordQueryHook(UBaseType_t* outLen) { + * *outLen = sizeof(DnsRecords) / sizeof(DnsRecords[0]); + * return DnsRecords; + * } + * + * extern void xApplicationDNSRecordsMatchedHook(void) { + * // Serve additional records according to RFC6763, section 12 + * // Instance Enumeration implies all other records + * if (DnsRecords[1].uxServeRecord == dnsRECORD_SERVE_ANSWER) { + * DnsRecords[2].uxServeRecord = dnsRECORD_SERVE_ADDITIONAL; + * DnsRecords[3].uxServeRecord = dnsRECORD_SERVE_ADDITIONAL; + * DnsRecords[4].uxServeRecord = dnsRECORD_SERVE_ADDITIONAL; + * } + * // SRV record implies A record + * if (DnsRecords[2].uxServeRecord == dnsRECORD_SERVE_ANSWER) { + * DnsRecords[4].uxServeRecord = dnsRECORD_SERVE_ADDITIONAL; + * } + * } + */ + +/* 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; + + /* How to serve this record. + * See `dnsRECORD_SERVE_NO`, `dnsRECORD_SERVE_ADDITIONAL` and `dnsRECORD_SERVE_ANSWER`. */ + BaseType_t uxServeRecord; + const char * pcName; + union + { + char const * pcPtrRecord; + struct + { + const char * pcTarget; + uint16_t usPort; + } xSrvRecord; + struct + { + const char * const * ppcStrings; + UBaseType_t uxStringCount; + } xTxtRecord; + } 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 ) */ + extern void xApplicationDNSRecordsMatchedHook( void ); + #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 { @@ -172,25 +299,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 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 - * 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. */ + 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 ) ) @@ -216,6 +346,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 ) @@ -283,24 +442,6 @@ size_t uxPayloadLength; /**< Payload size */ } DNSBuffer_t; - #if ( ipconfigUSE_MDNS == 1 ) || ( ipconfigUSE_LLMNR == 1 ) || ( ipconfigUSE_NBNS == 1 ) - -/* - * 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 BaseType_t xApplicationDNSQueryHook( 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 - - #endif /* ( 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 8c7252a38..9b92fc890 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,11 @@ 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 1b84a563e..6dbc5aedf 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,10 @@ static int callback_called = 0; static BaseType_t hook_return = pdFALSE; 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, @@ -96,6 +99,8 @@ void setUp( void ) { xBufferAllocFixedSize = pdFALSE; callback_called = 0; + prvDNSRecordsLen = 0; + prvDNSAdditionalRecordIndex = SIZE_MAX; } /** @@ -118,6 +123,46 @@ 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; +} + +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 ); + 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; +} + +static void SetDNSRecordServeAdditional( size_t n ) +{ + prvDNSAdditionalRecordIndex = n; +} + /* ============================= TEST CASES =============================== */ /** @@ -411,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 ); } @@ -479,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 ======================= */ /** @@ -1352,6 +1442,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 +1454,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 +1790,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, @@ -1737,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 ); @@ -1745,13 +1843,17 @@ 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 */ + + 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 +1876,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 +1954,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 +2027,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 +2089,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 +2100,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 +2177,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 +2247,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 +2299,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 +2322,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 +2374,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 +2404,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 +2476,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 +2505,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2515,8 +2552,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 +2581,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_null_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2589,7 +2628,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 +2670,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2681,8 +2722,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 +2764,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer2( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2773,8 +2816,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 +2858,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_new_netbuffer3( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2864,8 +2909,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 +2950,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_valid_fixed_buffer( void ) dns_header->usAnswers = FreeRTOS_htons( 2 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + pucUDPPayloadBuffer[ beg ] = 38; beg++; strcpy( pucUDPPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); @@ -2954,8 +3001,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 +3043,8 @@ void test_DNS_ParseDNSReply_answer_lmmnr_reply_fixed_buffer_full_content( void ) dns_header->usAnswers = FreeRTOS_htons( 0 ); dns_header->usFlags = dnsDNS_PORT; + SetDNSRecordsSimple( "FreeRTOSFreeRTOSFree" ); + /* First 5 queries have maximum length. */ /* DNS name field format requirements: @@ -3098,6 +3147,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 +3160,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, @@ -3122,6 +3171,847 @@ 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 ); + char const * const txtRecord[] = { "Some Text" }; + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_TXT, + .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; + + 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 ); + + char const * const txtRecord[] = + { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + }; + + prvDNSRecords[ 0 ] = ( DNSRecord_t ) { + .pcName = "FreeRTOS", + .usRecordType = dnsTYPE_TXT, + .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; + + 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_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 ); + + 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, + }; + + 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.xTxtRecord = + { + .ppcStrings = txtRecord, + .uxStringCount = 1 + } + }; + 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 ); + + 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_HOST ); +} + +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 ); + + 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 ); + + 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 ) +{ + /* 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 ); + + 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 ); + + memcpy( test_data.write_head, "\x00\x10\00\01", 4 ); + test_data.write_head += 4; + + xBufferAllocFixedSize = true; + + 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_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 @@ -4001,8 +4891,9 @@ void test_parseDNSAnswer_remaining_lt_dnsanswerrecord( void ) TEST_ASSERT_EQUAL( 40, uxBytesRead ); } -BaseType_t xApplicationDNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, - const char * pcName ) + +BaseType_t xApplicationNBNSQueryHook_Multi( struct xNetworkEndPoint * pxEndPoint, + const char * pcName ) { hook_called = pdTRUE; return hook_return; @@ -4062,6 +4953,8 @@ void test_DNS_ParseDNSReply_mdns_request( void ) strcpy( pucPayloadBuffer + beg, "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); beg += 38 + 1 + 4; /* Skip name, nul-byte, and Type/Class. */ + SetDNSRecordsSimple( "FreeRTOSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ); + /* xApplicationDNSQueryHook_Multi() must be called. */ hook_return = pdTRUE; /* it hasn't been called yet. */