From b5b6cf1766356c37912636a3836324beb25f38de Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 1 Feb 2021 22:12:05 -0800 Subject: [PATCH] [dns-header] new helper method to find i-th record matching name and type (#6124) This commit adds a new template helper method in `ResoureRecord` to search in a given message starting from a given offset and up to a maximum given number of records, for the i-th occurrence of a specific record (of given type with a matching record name). If found, it reads the record from the message into a given record object. This commit also updates the unit test `test_dns` to cover the behavior of newly added helper method. --- src/core/net/dns_headers.cpp | 59 ++++++++++++++++++++++++++++ src/core/net/dns_headers.hpp | 55 +++++++++++++++++++++++++- tests/unit/test_dns.cpp | 76 ++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp index 1a104465b..1d6891658 100644 --- a/src/core/net/dns_headers.cpp +++ b/src/core/net/dns_headers.cpp @@ -622,6 +622,65 @@ exit: return error; } +otError ResourceRecord::FindRecord(const Message & aMessage, + uint16_t & aOffset, + uint16_t aNumRecords, + uint16_t aIndex, + const Name & aName, + uint16_t aType, + ResourceRecord &aRecord, + uint16_t aMinRecordSize) +{ + // This static method searches in `aMessage` starting from `aOffset` + // up to maximum of `aNumRecords`, for the `(aIndex+1)`th + // occurrence of a resource record of type `aType` with record name + // matching `aName`. It also verifies that the record size is larger + // than `aMinRecordSize`. If found, `aMinRecordSize` bytes from the + // record are read and copied into `aRecord`. In this case `aOffset` + // is updated to point to the last record byte read from the message + // (so that the caller can read any remaining fields in the record + // data). + + otError error; + uint16_t offset = aOffset; + uint16_t recordOffset; + + while (aNumRecords > 0) + { + SuccessOrExit(error = FindRecord(aMessage, offset, aNumRecords, aName)); + + // Save the offset to start of `ResourceRecord` fields. + recordOffset = offset; + + error = ReadRecord(aMessage, offset, aType, aRecord, aMinRecordSize); + + if (error == OT_ERROR_NOT_FOUND) + { + // `ReadRecord()` already updates the `offset` to skip + // over a non-matching record. + continue; + } + + SuccessOrExit(error); + + if (aIndex == 0) + { + aOffset = offset; + ExitNow(); + } + + aIndex--; + + // Skip over the record. + offset = static_cast(recordOffset + aRecord.GetSize()); + } + + error = OT_ERROR_NOT_FOUND; + +exit: + return error; +} + otError ResourceRecord::ReadRecord(const Message & aMessage, uint16_t & aOffset, uint16_t aType, diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp index b1447eab1..93b9a4186 100644 --- a/src/core/net/dns_headers.hpp +++ b/src/core/net/dns_headers.hpp @@ -1200,6 +1200,50 @@ public: */ static otError FindRecord(const Message &aMessage, uint16_t &aOffset, uint16_t &aNumRecords, const Name &aName); + /** + * This template static method searches in a message to find the i-th occurrence of resource records of specific + * type with a given record name and if found, reads the record from the message. + * + * This method searches in @p aMessage starting from @p aOffset up to maximum of @p aNumRecords, for the + * `(aIndex+1)`th occurrence of a resource record of `RecordType` with record name @p aName. + * + * On success (i.e., when a matching record is found and read from the message), @p aOffset is updated to point + * to after the last byte read from the message and copied into @p aRecord. This allows the caller to read any + * remaining fields in the record data. + * + * @tparam RecordType The resource record type (i.e., a sub-class of `ResourceRecord`). + * + * @param[in] aMessage The message to search within for matching resource records. + * `aMessage.GetOffset()` MUST point to the start of DNS header. + * @param[inout] aOffset On input, the offset in @p aMessage pointing to the start of the first record. + * On exit and only if a matching record is found, @p aOffset is updated to point to + * the last read byte in the record (allowing caller to read any remaining fields in + * the record data from the message). + * @param[in] aNumRecords The maximum number of records to check (starting from @p aOffset). + * @param[in] aIndex The matching record index to find. @p aIndex value of zero returns the first + * matching record. + * @param[in] aName The record name to match against. + * @param[in] aRecord A reference to a record object to read a matching record into. + * If a matching record is found, `sizeof(RecordType)` bytes from @p aMessage are + * read and copied into @p aRecord. + * + * @retval OT_ERROR_NONE A matching record was found. @p aOffset is updated. + * @retval OT_ERROR_NOT_FOUND A matching record could not be found. + * @retval OT_ERROR_PARSE Could not parse records from @p aMessage (e.g., ran out of bytes in @p aMessage). + * + */ + template + static otError FindRecord(const Message &aMessage, + uint16_t & aOffset, + uint16_t aNumRecords, + uint16_t aIndex, + const Name & aName, + RecordType & aRecord) + { + return FindRecord(aMessage, aOffset, aNumRecords, aIndex, aName, RecordType::kType, aRecord, + sizeof(RecordType)); + } + /** * This template static method tries to read a resource record of a given type from a message. If the record type * does not matches the type, it skips over the record. @@ -1227,7 +1271,7 @@ public: * after the entire record (skipping over the record). * @param[out] aRecord A reference to a record to read a matching record into. * If a matching record is found, `sizeof(RecordType)` bytes from @p aMessage are - * read from @p aMessage and copied into @p aRecord. + * read and copied into @p aRecord. * * @retval OT_ERROR_NONE A matching record was read successfully. @p aOffset, and @p aRecord are updated. * @retval OT_ERROR_NOT_FOUND A matching record could not be found. @p aOffset is updated. @@ -1255,6 +1299,15 @@ private: kType = kTypeAny, // This is intended for used by `ReadRecord()` only. }; + static otError FindRecord(const Message & aMessage, + uint16_t & aOffset, + uint16_t aNumRecords, + uint16_t aIndex, + const Name & aName, + uint16_t aType, + ResourceRecord &aRecord, + uint16_t aMinRecordSize); + static otError ReadRecord(const Message & aMessage, uint16_t & aOffset, uint16_t aType, diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp index 2fac6fb46..b15018ce6 100644 --- a/tests/unit/test_dns.cpp +++ b/tests/unit/test_dns.cpp @@ -734,6 +734,7 @@ void TestHeaderAndResourceRecords(void) uint16_t hostNameOffset; uint16_t answerSectionOffset; uint16_t additionalSectionOffset; + uint16_t index; Dns::PtrRecord ptrRecord; Dns::SrvRecord srvRecord; Dns::TxtRecord txtRecord; @@ -1020,6 +1021,81 @@ void TestHeaderAndResourceRecords(void) offset += record.GetLength(); VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse"); + printf("Use FindRecord() to search for specific records:\n"); + printf(" Answer Section\n"); + + for (index = 0; index < OT_ARRAY_LENGTH(kInstanceNames); index++) + { + offset = answerSectionOffset; + SuccessOrQuit( + Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), ptrRecord), + "FindRecord() failed"); + + printf(" index:%d -> \"%s\" PTR %u %d\n", index, kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength()); + } + + // Check `FindRecord()` failure with non-matching name, record type, or bad index. + + offset = answerSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), + ptrRecord) == OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); + + offset = answerSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kInstance1Name), + ptrRecord) == OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); + + offset = answerSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), + txtRecord) == OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure"); + + printf(" Additional Section\n"); + + for (const char *instanceName : kInstanceNames) + { + // There is a single SRV and TXT entry for each instance + offset = additionalSectionOffset; + SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0, + Dns::Name(instanceName), srvRecord), + "FindRecord() failed"); + printf(" \"%s\" SRV %u %d %d %d %d \n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(), + srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority()); + + offset = additionalSectionOffset; + SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0, + Dns::Name(instanceName), txtRecord), + "FindRecord() failed"); + printf(" \"%s\" TXT %u %d\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength()); + + offset = additionalSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1, + Dns::Name(instanceName), srvRecord) == OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + + offset = additionalSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1, + Dns::Name(instanceName), txtRecord) == OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + } + + for (index = 0; index < kAdditionalCount; index++) + { + offset = additionalSectionOffset; + // Find record with empty name (matching any) and any type. + SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record), + "FindRecord() failed"); + } + + offset = additionalSectionOffset; + VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record) == + OT_ERROR_NOT_FOUND, + "FindRecord() did not fail with bad index"); + message->Free(); testFreeInstance(instance); }