[dns-header] new methods to compare label/name from message (#6078)

This commit adds new helper methods in `Dns::Name` to parse and
compare a single name label `CompareLabel()` or an entire name
`ComapareName()` from a given message with a given string. Another
flavor of `CompareName()` compares the names read from two different
messages (or same message at different offsets). The name checks are
performed in place (without reading/copying the content from the
messages) and the implementation handles compressed names.

Unit test `test_dns` is updated to verify behavior of newly added
methods.
This commit is contained in:
Abtin Keshavarzian
2021-01-24 21:45:31 -08:00
committed by GitHub
parent 6ee13b7a8d
commit 57bc0a3396
3 changed files with 388 additions and 3 deletions
+157
View File
@@ -292,6 +292,118 @@ exit:
return error;
}
otError Name::CompareLabel(const Message &aMessage, uint16_t &aOffset, const char *aLabel)
{
otError error;
LabelIterator iterator(aMessage, aOffset);
SuccessOrExit(error = iterator.GetNextLabel());
VerifyOrExit(iterator.CompareLabel(aLabel, /* aIsSingleLabel */ true), error = OT_ERROR_NOT_FOUND);
aOffset = iterator.mNextLabelOffset;
exit:
return error;
}
otError Name::CompareName(const Message &aMessage, uint16_t &aOffset, const char *aName)
{
otError error;
LabelIterator iterator(aMessage, aOffset);
bool matches = true;
if (*aName == kLabelSeperatorChar)
{
aName++;
VerifyOrExit(*aName == kNullChar, error = OT_ERROR_INVALID_ARGS);
}
while (true)
{
error = iterator.GetNextLabel();
switch (error)
{
case OT_ERROR_NONE:
if (matches && !iterator.CompareLabel(aName, /* aIsSingleLabel */ false))
{
matches = false;
}
break;
case OT_ERROR_NOT_FOUND:
// We reached the end of the name in `aMessage`. We check if
// all the previous labels matched so far, and we are also
// at the end of `aName` string (see null char), then we
// return `OT_ERROR_NONE` indicating a successful comparison
// (full match). Otherwise we return `OT_ERROR_NOT_FOUND` to
// indicate failed comparison.
if (matches && (*aName == kNullChar))
{
error = OT_ERROR_NONE;
}
aOffset = iterator.mNameEndOffset;
OT_FALL_THROUGH;
default:
ExitNow();
}
}
exit:
return error;
}
otError Name::CompareName(const Message &aMessage, uint16_t &aOffset, const Message &aMessage2, uint16_t aOffset2)
{
otError error;
LabelIterator iterator(aMessage, aOffset);
LabelIterator iterator2(aMessage2, aOffset2);
bool matches = true;
while (true)
{
error = iterator.GetNextLabel();
switch (error)
{
case OT_ERROR_NONE:
// If all the previous labels matched so far, then verify
// that we can get the next label on `iterator2` and that it
// matches the label from `iterator`.
if (matches && (iterator2.GetNextLabel() != OT_ERROR_NONE || !iterator.CompareLabel(iterator2)))
{
matches = false;
}
break;
case OT_ERROR_NOT_FOUND:
// We reached the end of the name in `aMessage`. We check
// that `iterator2` is also at its end, and if all previous
// labels matched we return `OT_ERROR_NONE`.
if (matches && (iterator2.GetNextLabel() == OT_ERROR_NOT_FOUND))
{
error = OT_ERROR_NONE;
}
aOffset = iterator.mNameEndOffset;
OT_FALL_THROUGH;
default:
ExitNow();
}
}
exit:
return error;
}
otError Name::LabelIterator::GetNextLabel(void)
{
otError error;
@@ -374,6 +486,51 @@ exit:
return error;
}
bool Name::LabelIterator::CompareLabel(const char *&aName, bool aIsSingleLabel) const
{
// This method compares the current label in the iterator with the
// `aName` string. `aIsSingleLabel` indicates whether `aName` is a
// single label, or a sequence of labels separated by dot '.' char.
// If the label matches `aName`, then `aName` pointer is moved
// forward to the start of the next label (skipping over the `.`
// char). This method returns `true` when the labels match, `false`
// otherwise.
bool matches = false;
VerifyOrExit(StringLength(aName, mLabelLength) == mLabelLength);
matches = mMessage.CompareBytes(mLabelStartOffset, aName, mLabelLength);
VerifyOrExit(matches);
aName += mLabelLength;
// If `aName` is a single label, we should be also at the end of the
// `aName` string. Otherwise, we should see either null or dot '.'
// character (in case `aName` contains multiple labels).
matches = (*aName == kNullChar);
if (!aIsSingleLabel && (*aName == kLabelSeperatorChar))
{
matches = true;
aName++;
}
exit:
return matches;
}
bool Name::LabelIterator::CompareLabel(const LabelIterator &aOtherIterator) const
{
// This method compares the current label in the iterator with the
// label from another iterator.
return (mLabelLength == aOtherIterator.mLabelLength) &&
mMessage.CompareBytes(mLabelStartOffset, aOtherIterator.mMessage, aOtherIterator.mLabelStartOffset,
mLabelLength);
}
bool AaaaRecord::IsValid(void) const
{
return GetType() == Dns::ResourceRecord::kTypeAaaa && GetSize() == sizeof(*this);
+89 -3
View File
@@ -565,7 +565,7 @@ public:
* This static method encodes and appends a full name to a message.
*
* The @p aName must follow "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
* E.g., "example.com", "example.com." (same as previous one), local.", "default.service.arpa", "." or "" (root).
* E.g., "example.com", "example.com." (same as previous one), "local.", "default.service.arpa", "." or "" (root).
*
* This method validates that the @p aName is a valid name format, i.e. no empty labels, and labels are
* `kMaxLabelLength` (63) characters or less, and the name is `kMaxLength` (255) characters or less.
@@ -614,7 +614,7 @@ public:
* the next label.
* @param[out] aLabelBuffer A pointer to a char array to output the read label as a null-terminated C string.
* @param[inout] aLabelLength On input, the maximum number chars in @p aLabelBuffer array.
* On output, when label is successfully read, @aLabelLength is updated to return
* On output, when label is successfully read, @p aLabelLength is updated to return
* the label's length (number of chars in the label string, excluding the null char).
*
* @retval OT_ERROR_NONE Successfully read the label and updated @p aLabelBuffer, @p aLabelLength, and
@@ -641,7 +641,7 @@ public:
* On exit (when parsed successfully), @p aOffset is updated to point to the byte
* after the end of name field.
* @param[out] aNameBuffer A pointer to a char array to output the read name as a null-terminated C string.
* @param[inout] aNameBufferSize The maximum number chars in @p aNameBuffer array.
* @param[inout] aNameBufferSize The maximum number of chars in @p aNameBuffer array.
*
* @retval OT_ERROR_NONE Successfully read the name, @p aNameBuffer and @p Offset are updated.
* @retval OT_ERROR_PARSE Name could not be parsed (invalid format).
@@ -650,6 +650,90 @@ public:
*/
static otError ReadName(const Message &aMessage, uint16_t &aOffset, char *aNameBuffer, uint16_t aNameBufferSize);
/**
* This static method compares a single name label from a message with a given label string.
*
* This method can be used to compare labels one by one. It checks whether the label read from @p aMessage matches
* @p aLabel string.
*
* Unlike `CompareName()` which requires the labels in the the name string to contain no dot '.' character, this
* method allows @p aLabel to include any character.
*
* @param[in] aMessage The message to read the label from to compare. `aMessage.GetOffset()` MUST point
* to the start of DNS header (this is used to handle compressed names).
* @param[inout] aOffset On input, the offset in @p aMessage pointing to the start of the label to read.
* On exit and only when label is successfully read and does match @p aLabel,
* @p aOffset is updated to point to the start of the next label.
* @param[in] aLabel A pointer to a null terminated string containing the label to compare with.
* @retval OT_ERROR_NONE The label from @p aMessage matches @p aLabel. @p aOffset is updated.
* @retval OT_ERROR_NOT_FOUND The label from @p aMessage does not match @p aLabel (note that @p aOffset is not
* updated in this case).
* @retval OT_ERROR_PARSE Name could not be parsed (invalid format).
*
*/
static otError CompareLabel(const Message &aMessage, uint16_t &aOffset, const char *aLabel);
/**
* This static method parses and compares a full name from a message with a given name.
*
* This method checks whether the encoded name in a message matches a given name string. It checks the name in
* the message in place and handles compressed names. If the name read from the message does not match @p aName, it
* returns `OT_ERROR_NOT_FOUND`. `OT_ERROR_NONE` indicates that the name matches @p aName.
*
* The @p aName must follow "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
* E.g., "example.com", "example.com." (same as previous one), "local.", "default.service.arpa", "." or "" (root).
*
* @param[in] aMessage The message to read the name from and compare with @p aName.
* `aMessage.GetOffset()` MUST point to the start of DNS header (this is used to
* handle compressed names).
* @param[inout] aOffset On input, the offset in @p aMessage pointing to the start of the name field.
* On exit (when parsed successfully independent of whether the read name matches
* @p aName or not), @p aOffset is updated to point to the byte after the end of
* the name field.
* @param[in] aName A pointer to a null terminated string containing the name to compare with.
*
* @retval OT_ERROR_NONE The name from @p aMessage matches @p aName. @p aOffset is updated.
* @retval OT_ERROR_NOT_FOUND The name from @p aMessage does not match @p aName. @p aOffset is updated.
* @retval OT_ERROR_PARSE Name could not be parsed (invalid format).
* @retval OT_ERROR_INVALID_ARGS The @p aName is not a valid name (e.g. back to back "." chars)
*
*/
static otError CompareName(const Message &aMessage, uint16_t &aOffset, const char *aName);
/**
* This static method parses and compares a full name from a message with a name from another message.
*
* This method checks whether the encoded name in @p aMessage matches the name from @p aMessage2. It compares the
* names in both messages in place and handles compressed names. Note that this method works correctly even when
* the same message instance is used for both @p aMessage and @p aMessage2 (e.g., at different offsets).
*
* Only the name in @p aMessage is fully parsed and checked for parse errors. This method assumes that the name in
* @p aMessage2 was previously parsed and validated before calling this method (if there is a parse error in
* @p aMessage2, it is treated as a name mismatch with @p aMessage).
*
* If the name in @p aMessage can be parsed fully (independent of whether the name matches or not with the name
* from @p aMessage2), the @p aOffset is updated (note that @p aOffset2 for @p aMessage2 is not changed).
*
* @param[in] aMessage The message to read the name from and compare. `aMessage.GetOffset()` MUST point
* to the start of DNS header (this is used to handle compressed names).
* @param[inout] aOffset On input, the offset in @p aMessage pointing to the start of the name field.
* On exit (when parsed successfully independent of whether the read name matches
* or not), @p aOffset is updated to point to the byte after the end of the name
* field.
* @param[in] aMessage2 The second message to read the name from and compare with name from @p aMessage.
* `aMessage2.GetOffset()` MUST point to the start of DNS header.
* @param[in] aOffset2 The offset in @p aMessage2 pointing to the start of the name field.
*
* @retval OT_ERROR_NONE The name from @p aMessage matches the name from @p aMessage2. @p aOffset is
* updated.
* @retval OT_ERROR_NOT_FOUND The name from @p aMessage does not match the name from @p aMessage2. @p aOffset
* is updated.
* @retval OT_ERROR_PARSE Name in @p aMessage could not be parsed (invalid format).
*
*/
static otError CompareName(const Message &aMessage, uint16_t &aOffset, const Message &aMessage2, uint16_t aOffset2);
private:
enum : char
{
@@ -693,6 +777,8 @@ private:
bool IsEndOffsetSet(void) const { return (mNameEndOffset != kUnsetNameEndOffset); }
otError GetNextLabel(void);
otError ReadLabel(char *aLabelBuffer, uint8_t &aLabelLength, bool aAllowDotCharInLabel) const;
bool CompareLabel(const char *&aName, bool aIsSingleLabel) const;
bool CompareLabel(const LabelIterator &aOtherIterator) const;
const Message &mMessage; // Message to read labels from.
uint16_t mLabelStartOffset; // Offset in `mMessage` to the first char of current label text.
+142
View File
@@ -131,6 +131,9 @@ void TestDnsName(void)
};
static const char kBadLabel[] = "badlabel";
static const char kBadName[] = "bad.name";
printf("================================================================\n");
printf("TestDnsName()\n");
@@ -200,6 +203,54 @@ void TestDnsName(void)
VerifyOrQuit(Dns::Name::ReadName(*message, offset, name,
static_cast<uint16_t>(strlen(test.mExpectedReadName))) == OT_ERROR_NO_BUFS,
"Name::ReadName() did not fail with too small name buffer size");
// Compare labels one by one.
offset = 0;
for (uint8_t index = 0; test.mLabels[index] != nullptr; index++)
{
uint16_t startOffset = offset;
SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, test.mLabels[index]),
"Name::CompareLabel() failed");
VerifyOrQuit(offset != startOffset, "Name::CompareLabel() did not change offset");
VerifyOrQuit(Dns::Name::CompareLabel(*message, startOffset, kBadLabel) == OT_ERROR_NOT_FOUND,
"Name::CompareLabel() did not fail with incorrect label");
}
// Compare the whole name.
offset = 0;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, test.mExpectedReadName), "Name::CompareName() failed");
VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
offset = 0;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with incorrect name");
VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
// Remove the terminating '.' in expected name and verify
// that it can still be used by `CompareName()`.
offset = 0;
strcpy(name, test.mExpectedReadName);
name[strlen(name) - 1] = '\0';
SuccessOrQuit(Dns::Name::CompareName(*message, offset, name), "Name::CompareName() failed with root");
VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
if (strlen(name) >= 1)
{
name[strlen(name) - 1] = '\0';
offset = 0;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, name) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with invalid name");
VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
}
// Compare the name with itself read from message.
offset = 0;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset),
"Name::CompareName() with itself failed");
VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
}
printf("----------------------------------------------------------------\n");
@@ -315,6 +366,8 @@ void TestDnsCompressedName(void)
static const char kExpectedReadName2[] = "FOO.F.ISI.ARPA.";
static const char kExpectedReadName3[] = "ISI.ARPA.";
static const char kBadName[] = "bad.name";
Instance * instance;
MessagePool *messagePool;
Message * message;
@@ -416,6 +469,31 @@ void TestDnsCompressedName(void)
VerifyOrQuit(strcmp(name, kExpectedReadName1) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ReadName() returned incorrect offset");
offset = name1Offset;
for (const char *nameLabel : kName1Labels)
{
SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
}
offset = name1Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName1), "Name::CompareName() failed");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
offset = name1Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with incorrect name");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
offset = name1Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
offset = name1Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name2Offset) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with mismatching name");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse compressed name-2 \"FOO.F.ISI.ARPA\"\n");
@@ -448,6 +526,31 @@ void TestDnsCompressedName(void)
VerifyOrQuit(strcmp(name, kExpectedReadName2) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ReadName() returned incorrect offset");
offset = name2Offset;
for (const char *nameLabel : kName2Labels)
{
SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
}
offset = name2Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName2), "Name::CompareName() failed");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name2Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with incorrect name");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name2Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name2Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name3Offset) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with mismatching name");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse compressed name-3 \"ISI.ARPA\"\n");
@@ -480,6 +583,31 @@ void TestDnsCompressedName(void)
VerifyOrQuit(strcmp(name, kExpectedReadName3) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ReadName() returned incorrect offset");
offset = name3Offset;
for (const char *nameLabel : kName3Labels)
{
SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
}
offset = name3Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName3), "Name::CompareName() failed");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name3Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with incorrect name");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name3Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
offset = name3Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name4Offset) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with mismatching name");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse the uncompressed name-4 \"Human\\.Readable.F.ISI.ARPA\"\n");
@@ -507,6 +635,20 @@ void TestDnsCompressedName(void)
VerifyOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)) == OT_ERROR_PARSE,
"Name::ReadName() did not fail with invalid label");
offset = name4Offset;
for (const char *nameLabel : kName4Labels)
{
SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
}
offset = name4Offset;
SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
offset = name4Offset;
VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name1Offset) == OT_ERROR_NOT_FOUND,
"Name::CompareName() did not fail with mismatching name");
message->Free();
testFreeInstance(instance);
}