Files
openthread/tests/unit/test_dns.cpp
T
Abtin Keshavarzian 73126f7099 [dns] add helper methods to append/parse DNS names (#5922)
This commit adds `Dns::Name` type which provides a set of helper
methods to encode/decode DNS names. `AppendName()` method encodes and
appends a full name (e.g., "test.example.com") to a message. Other
helper methods enable appending labels (or groups of labels) and/or
constructing a compressed name (using pointer labels). `ParseName()`
method parses and skips over a full name in a message. `ReadLabel()`
method reads labels one by one and works independently of whether the
encoded name is compressed or not. `ReadName()` method read an entire
name from message. The new methods are used in `Dns::Client`.

This commit also add a unit test `test-dns` covering the behavior of
the newly added helper methods.

Finally, this commit adds few helper methods in `ResourceRecords` to
get the size and check the type of a DNS record.
2020-12-08 12:20:19 -08:00

477 lines
20 KiB
C++

/*
* Copyright (c) 2020, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h>
#include <openthread/config.h>
#include "test_platform.h"
#include "test_util.hpp"
#include "common/instance.hpp"
#include "net/dns_headers.hpp"
namespace ot {
void TestDnsName(void)
{
enum
{
kMaxSize = 300,
kLabelSize = 64,
kNameSize = 256,
};
struct TestName
{
const char * mName;
uint16_t mEncodedLength;
const uint8_t *mEncodedData;
const char ** mLabels;
const char * mExpectedReadName;
};
Instance * instance;
MessagePool *messagePool;
Message * message;
uint8_t buffer[kMaxSize];
uint16_t len;
uint16_t offset;
char label[kLabelSize];
uint8_t labelLength;
char name[kNameSize];
static const uint8_t kEncodedName1[] = {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0};
static const uint8_t kEncodedName2[] = {3, 'f', 'o', 'o', 1, 'a', 2, 'b', 'b', 3, 'e', 'd', 'u', 0};
static const uint8_t kEncodedName3[] = {10, 'f', 'o', 'u', 'n', 'd', 'a', 't', 'i', 'o', 'n', 0};
static const uint8_t kEncodedName4[] = {0};
static const char *kLabels1[] = {"example", "com", nullptr};
static const char *kLabels2[] = {"foo", "a", "bb", "edu", nullptr};
static const char *kLabels3[] = {"foundation", nullptr};
static const char *kLabels4[] = {nullptr};
static const TestName kTestNames[] = {
{"example.com", sizeof(kEncodedName1), kEncodedName1, kLabels1, "example.com."},
{"example.com.", sizeof(kEncodedName1), kEncodedName1, kLabels1, "example.com."},
{"foo.a.bb.edu", sizeof(kEncodedName2), kEncodedName2, kLabels2, "foo.a.bb.edu."},
{"foo.a.bb.edu.", sizeof(kEncodedName2), kEncodedName2, kLabels2, "foo.a.bb.edu."},
{"foundation", sizeof(kEncodedName3), kEncodedName3, kLabels3, "foundation."},
{"foundation.", sizeof(kEncodedName3), kEncodedName3, kLabels3, "foundation."},
{"", sizeof(kEncodedName4), kEncodedName4, kLabels4, "."},
{".", sizeof(kEncodedName4), kEncodedName4, kLabels4, "."},
{nullptr, sizeof(kEncodedName4), kEncodedName4, kLabels4, "."},
};
static const char *kInvalidNames[] = {
"foo..bar",
"..",
"a..",
"..b",
// Long label
"a.an-invalid-very-long-label-string-with-more-than-sixty-four-characters.com",
// Long name (more than 255 characters)
"HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken."
"HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken."
"HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken."
"SorcererAndMagician.NoEyesCanEverSee.AnArtfulConjurer.MySenseFromMeTaken."
"MyEyesWillNeverSee.BeautiesOfTheWholeWorld.BeholdWhoseVisionFine.MySightFromMeTaken"
"PoemByRumiMolana",
};
printf("================================================================\n");
printf("TestDnsName()\n");
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
messagePool = &instance->Get<MessagePool>();
VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
printf("----------------------------------------------------------------\n");
printf("Append names, check encoded bytes, parse name and read labels:\n");
for (const TestName &test : kTestNames)
{
IgnoreError(message->SetLength(0));
SuccessOrQuit(Dns::Name::AppendName(test.mName, *message), "Name::AppendName() failed");
len = message->GetLength();
SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed");
DumpBuffer(test.mName, buffer, len);
VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value");
VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data");
// Parse and skip over the name
offset = 0;
SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed");
VerifyOrQuit(offset == len, "Name::ParseName() returned incorrect offset");
// Read labels one by one.
offset = 0;
for (uint8_t index = 0; test.mLabels[index] != nullptr; index++)
{
labelLength = sizeof(label);
SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, 0, label, labelLength), "Name::ReadLabel() failed");
printf("Label[%d] = \"%s\"\n", index, label);
VerifyOrQuit(strcmp(label, test.mLabels[index]) == 0, "Name::ReadLabel() did not get expected label");
VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length");
}
labelLength = sizeof(label);
VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, 0, label, labelLength) == OT_ERROR_NOT_FOUND,
"Name::ReadLabel() failed at end of the name");
// Read entire name
offset = 0;
SuccessOrQuit(Dns::Name::ReadName(*message, offset, 0, name, sizeof(name)), "Name::ReadName() failed");
printf("Read name =\"%s\"\n", name);
VerifyOrQuit(strcmp(name, test.mExpectedReadName) == 0, "Name::ReadName() did not get expected name");
VerifyOrQuit(offset == len, "Name::ReadName() returned incorrect offset");
// Read entire name with different name buffer sizes (just right and one byte off the expected size)
offset = 0;
SuccessOrQuit(
Dns::Name::ReadName(*message, offset, 0, name, static_cast<uint16_t>(strlen(test.mExpectedReadName) + 1)),
"Name::ReadName() failed with exact name buffer size");
offset = 0;
VerifyOrQuit(Dns::Name::ReadName(*message, offset, 0, name,
static_cast<uint16_t>(strlen(test.mExpectedReadName))) == OT_ERROR_NO_BUFS,
"Name::ReadName() did not fail with too small name buffer size");
}
printf("----------------------------------------------------------------\n");
printf("Invalid names:\n");
for (const char *&invalidName : kInvalidNames)
{
IgnoreError(message->SetLength(0));
printf("\"%s\"\n", invalidName);
VerifyOrQuit(Dns::Name::AppendName(invalidName, *message) == OT_ERROR_INVALID_ARGS,
"Name::AppendName() did not fail with an invalid name");
}
printf("----------------------------------------------------------------\n");
printf("Append as multiple labels and terminator instead of full name:\n");
for (const TestName &test : kTestNames)
{
IgnoreError(message->SetLength(0));
SuccessOrQuit(Dns::Name::AppendMultipleLabels(test.mName, *message), "Name::Append() failed");
SuccessOrQuit(Dns::Name::AppendTerminator(*message), "Name::AppendTerminator() failed");
len = message->GetLength();
SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed");
DumpBuffer(test.mName, buffer, len);
VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value");
VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data");
}
printf("----------------------------------------------------------------\n");
printf("Append labels one by one:\n");
for (const TestName &test : kTestNames)
{
IgnoreError(message->SetLength(0));
for (uint8_t index = 0; test.mLabels[index] != nullptr; index++)
{
SuccessOrQuit(Dns::Name::AppendLabel(test.mLabels[index], *message), "Name::AppendLabel() failed");
}
SuccessOrQuit(Dns::Name::AppendTerminator(*message), "Name::AppendTerminator() failed");
len = message->GetLength();
SuccessOrQuit(message->Read(0, buffer, len), "Message::Read() failed");
DumpBuffer(test.mName, buffer, len);
VerifyOrQuit(len == test.mEncodedLength, "Encoded length does not match expected value");
VerifyOrQuit(memcmp(buffer, test.mEncodedData, len) == 0, "Encoded name data does not match expected data");
}
message->Free();
testFreeInstance(instance);
}
void TestDnsCompressedName(void)
{
enum
{
kHeaderOffset = 10,
kGuardBlockSize = 20,
kMaxBufferSize = 100,
kLabelSize = 64,
kNameSize = 256,
kName2EncodedSize = 4 + 2, // encoded "FOO" + pointer label (2 bytes)
kName3EncodedSize = 2, // pointer label (2 bytes)
kName4EncodedSize = 15 + 2, // encoded "Human.Readable" + pointer label (2 bytes).
};
const char kName[] = "F.ISI.ARPA";
const char kLabel1[] = "FOO";
const char kInstanceLabel[] = "Human.Readable";
static const uint8_t kEncodedName[] = {1, 'F', 3, 'I', 'S', 'I', 4, 'A', 'R', 'P', 'A', 0};
static const uint8_t kIsiRelativeIndex = 2; // Index in kEncodedName to the start of "ISI.ARPA" portion.
static const char *kName1Labels[] = {"F", "ISI", "ARPA"};
static const char *kName2Labels[] = {"FOO", "F", "ISI", "ARPA"};
static const char *kName3Labels[] = {"ISI", "ARPA"};
static const char *kName4Labels[] = {"Human.Readable", "F", "ISI", "ARPA"};
static const char kExpectedReadName1[] = "F.ISI.ARPA.";
static const char kExpectedReadName2[] = "FOO.F.ISI.ARPA.";
static const char kExpectedReadName3[] = "ISI.ARPA.";
Instance * instance;
MessagePool *messagePool;
Message * message;
uint16_t offset;
uint16_t name1Offset;
uint16_t name2Offset;
uint16_t name3Offset;
uint16_t name4Offset;
uint8_t buffer[kMaxBufferSize];
char label[kLabelSize];
uint8_t labelLength;
char name[kNameSize];
printf("================================================================\n");
printf("TestDnsCompressedName()\n");
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
messagePool = &instance->Get<MessagePool>();
VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
// Append name1 "F.ISI.ARPA"
for (uint8_t index = 0; index < kHeaderOffset + kGuardBlockSize; index++)
{
SuccessOrQuit(message->Append(index), "Message::Append() failed");
}
name1Offset = message->GetLength();
SuccessOrQuit(Dns::Name::AppendName(kName, *message), "Name::AppendName() failed");
// Append name2 "FOO.F.ISI.ARPA" as a compressed name after some guard/extra bytes
for (uint8_t index = 0; index < kGuardBlockSize; index++)
{
uint8_t value = 0xff;
SuccessOrQuit(message->Append(value), "Message::Append() failed");
}
name2Offset = message->GetLength();
SuccessOrQuit(Dns::Name::AppendLabel(kLabel1, *message), "Name::AppendLabel() failed");
SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset - kHeaderOffset, *message),
"Name::AppendPointerLabel() failed");
// Append name3 "ISI.ARPA" as a compressed name after some guard/extra bytes
for (uint8_t index = 0; index < kGuardBlockSize; index++)
{
uint8_t value = 0xaa;
SuccessOrQuit(message->Append(value), "Message::Append() failed");
}
name3Offset = message->GetLength();
SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset + kIsiRelativeIndex - kHeaderOffset, *message),
"Name::AppendPointerLabel() failed");
name4Offset = message->GetLength();
SuccessOrQuit(Dns::Name::AppendLabel(kInstanceLabel, *message), "Name::AppendLabel() failed");
SuccessOrQuit(Dns::Name::AppendPointerLabel(name1Offset - kHeaderOffset, *message),
"Name::AppendPointerLabel() failed");
printf("----------------------------------------------------------------\n");
printf("Read and parse the uncompressed name-1 \"F.ISI.ARPA\"\n");
SuccessOrQuit(message->Read(name1Offset, buffer, sizeof(kEncodedName)), "Message::Read() failed");
DumpBuffer(kName, buffer, sizeof(kEncodedName));
VerifyOrQuit(memcmp(buffer, kEncodedName, sizeof(kEncodedName)) == 0,
"Encoded name data does not match expected data");
offset = name1Offset;
SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ParseName() returned incorrect offset");
offset = name1Offset;
for (const char *nameLabel : kName1Labels)
{
labelLength = sizeof(label);
SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
"Name::ReadLabel() failed");
printf("label: \"%s\"\n", label);
VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length");
}
labelLength = sizeof(label);
VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
"Name::ReadLabel() failed at end of the name");
offset = name1Offset;
SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
printf("Read name =\"%s\"\n", name);
VerifyOrQuit(strcmp(name, kExpectedReadName1) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ReadName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse compressed name-2 \"FOO.F.ISI.ARPA\"\n");
SuccessOrQuit(message->Read(name2Offset, buffer, kName2EncodedSize), "Message::Read() failed");
DumpBuffer("name2(compressed)", buffer, kName2EncodedSize);
offset = name2Offset;
SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ParseName() returned incorrect offset");
offset = name2Offset;
for (const char *nameLabel : kName2Labels)
{
labelLength = sizeof(label);
SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
"Name::ReadLabel() failed");
printf("label: \"%s\"\n", label);
VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length");
}
labelLength = sizeof(label);
VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
"Name::ReadLabel() failed at end of the name");
offset = name2Offset;
SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
printf("Read name =\"%s\"\n", name);
VerifyOrQuit(strcmp(name, kExpectedReadName2) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ReadName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse compressed name-3 \"ISI.ARPA\"\n");
SuccessOrQuit(message->Read(name3Offset, buffer, kName3EncodedSize), "Message::Read() failed");
DumpBuffer("name2(compressed)", buffer, kName3EncodedSize);
offset = name3Offset;
SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ParseName() returned incorrect offset");
offset = name3Offset;
for (const char *nameLabel : kName3Labels)
{
labelLength = sizeof(label);
SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
"Name::ReadLabel() failed");
printf("label: \"%s\"\n", label);
VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length");
}
labelLength = sizeof(label);
VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
"Name::ReadLabel() failed at end of the name");
offset = name3Offset;
SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
printf("Read name =\"%s\"\n", name);
VerifyOrQuit(strcmp(name, kExpectedReadName3) == 0, "Name::ReadName() did not return expected name");
VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ReadName() returned incorrect offset");
printf("----------------------------------------------------------------\n");
printf("Read and parse the uncompressed name-4 \"Human\\.Readable.F.ISI.ARPA\"\n");
SuccessOrQuit(message->Read(name4Offset, buffer, kName4EncodedSize), "Message::Read() failed");
DumpBuffer("name4(compressed)", buffer, kName4EncodedSize);
offset = name4Offset;
SuccessOrQuit(Dns::Name::ParseName(*message, offset), "Name::ParseName() failed");
VerifyOrQuit(offset == name4Offset + kName4EncodedSize, "Name::ParseName() returned incorrect offset");
offset = name4Offset;
for (const char *nameLabel : kName4Labels)
{
labelLength = sizeof(label);
SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
"Name::ReadLabel() failed");
printf("label: \"%s\"\n", label);
VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
VerifyOrQuit(labelLength == strlen(label), "Name::ReadLabel() returned incorrect label length");
}
// `ReadName()` for name-4 should fails due to first label containing dot char.
offset = name4Offset;
VerifyOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)) == OT_ERROR_PARSE,
"Name::ReadName() did not fail with invalid label");
message->Free();
testFreeInstance(instance);
}
} // namespace ot
int main(void)
{
ot::TestDnsName();
ot::TestDnsCompressedName();
printf("All tests passed\n");
return 0;
}