[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.
This commit is contained in:
Abtin Keshavarzian
2021-02-01 22:12:05 -08:00
committed by GitHub
parent e49d20ff02
commit b5b6cf1766
3 changed files with 189 additions and 1 deletions
+59
View File
@@ -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<uint16_t>(recordOffset + aRecord.GetSize());
}
error = OT_ERROR_NOT_FOUND;
exit:
return error;
}
otError ResourceRecord::ReadRecord(const Message & aMessage,
uint16_t & aOffset,
uint16_t aType,
+54 -1
View File
@@ -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 <class RecordType>
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,
+76
View File
@@ -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);
}