#include <config.h>
+#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option_dnr.h>
+using namespace isc::asiolink;
+
namespace isc {
namespace dhcp {
OptionDnr6::OptionDnr6(OptionBufferConstIter begin, OptionBufferConstIter end)
- : Option(V6, D6O_V6_DNR), addr_length_(0) {
+ : Option(V6, D6O_V6_DNR) {
unpack(begin, end);
}
void
OptionDnr6::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
if (std::distance(begin, end) < getMinimalLength()) {
- isc_throw(OutOfRange, "parsed DHCPv6 Encrypted DNS Option ("
- << D6O_V6_DNR << ") data truncated to"
+ isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: "
+ "data truncated to"
" size " << std::distance(begin, end));
}
setData(begin, end);
// First two octets of Option data is Service Priority - this is mandatory field.
service_priority_ = isc::util::readUint16((&*begin), SERVICE_PRIORITY_SIZE);
- begin += sizeof(service_priority_);
+ begin += SERVICE_PRIORITY_SIZE;
- // Next comes two octets of ADN Length plus the ADN data itself (variable length).
+ // Next come two octets of ADN Length plus the ADN data itself (variable length).
// This is Opaque Data Tuple so let's use this class to retrieve the ADN data.
OpaqueDataTuple adn_tuple(OpaqueDataTuple::LENGTH_2_BYTES, begin, end);
adn_length_ = adn_tuple.getLength();
- if (adn_length_ > 0) {
- // Let's try to extract ADN FQDN data
- isc::util::InputBuffer name_buf(&adn_tuple.getData()[0], adn_length_);
- try {
- adn_.reset(new isc::dns::Name(name_buf, true));
- } catch (const Exception& ex) {
- isc_throw(InvalidOptionDnr6DomainName, "failed to parse "
- "fully qualified domain-name from wire format "
- "- " << ex.what());
- }
+
+ // Encrypted DNS options are designed to always include an authentication domain name,
+ // so when there is no FQDN included, we shall throw an exception.
+ if (adn_length_ == 0) {
+ isc_throw(InvalidOptionDnr6DomainName, "Mandatory Authentication Domain Name fully "
+ "qualified domain-name is missing");
}
+
+ // Let's try to extract ADN FQDN data.
+ isc::util::InputBuffer name_buf(&adn_tuple.getData()[0], adn_length_);
+ try {
+ adn_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOptionDnr6DomainName, "failed to parse "
+ "fully qualified domain-name from wire format "
+ "- " << ex.what());
+ }
+
begin += adn_tuple.getTotalLength();
if (begin == end) {
- // ADN only mode, other fields are not included
+ // ADN only mode, other fields are not included.
return;
}
if (std::distance(begin, end) < ADDR_LENGTH_SIZE) {
- isc_throw(OutOfRange, "truncated DHCPv6 Encrypted DNS Option (" << D6O_V6_DNR << ") - after"
- " ADN field, there should be at least 2 bytes long Addr Length field");
+ isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ") malformed: after"
+ " ADN field, there should be at least "
+ "2 bytes long Addr Length field");
}
+ // Next come two octets of Addr Length.
addr_length_ = isc::util::readUint16((&*begin), ADDR_LENGTH_SIZE);
- // TBD
+ begin += ADDR_LENGTH_SIZE;
+ // It MUST be a multiple of 16.
+ if ((addr_length_ % V6ADDRESS_LEN) != 0) {
+ isc_throw(OutOfRange, "DHCPv6 Encrypted DNS Option (" << type_ << ")"
+ << " malformed: Addr Len=" << addr_length_
+ << " is not divisible by 16.");
+ }
+
+ // Let's unpack the ipv6-address(es).
+ auto addr_end = begin + addr_length_;
+ while (begin != addr_end) {
+ ipv6_addresses_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
+ begin += V6ADDRESS_LEN;
+ }
+
+ // SvcParams (variable length) field is last
+ svc_params_length_ = std::distance(begin, end);
}
std::string
#include <config.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option_dnr.h>
#include <boost/scoped_ptr.hpp>
EXPECT_EQ(20, option->getAdnLength());
EXPECT_EQ("myhost.example.com.", option->getAdn());
EXPECT_EQ(0, option->getAddrLength());
+ EXPECT_EQ(0, option->getSvcParamsLength());
}
TEST(OptionDnr6Test, constructorDataTruncated) {
ASSERT_FALSE(option);
}
+TEST(OptionDnr6Test, noAdnFqdn) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x01, // Service priority is 1 dec
+ 0x00, 0x00 // ADN Length is 0 dec
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Encrypted DNS options are designed to ALWAYS include
+ // an authentication domain name, so check that constructor throws
+ // InvalidOptionDnr6DomainName exception.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), InvalidOptionDnr6DomainName);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, truncatedFqdn) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74 // FQDN data is truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OpaqueDataTupleError exception.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OpaqueDataTupleError);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, addrLenTruncated) {
+ // Prepare data to decode
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x10 // Truncated Addr Len field
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, addrLenZero) {
+ // Prepare data to decode
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x00 // Addr Len field value = 0
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_NO_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(0x8001, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ("myhost.example.com.", option->getAdn());
+ EXPECT_EQ(0, option->getAddrLength());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+}
+
+TEST(OptionDnr6Test, addrLenNot16Modulo) {
+ // Prepare data to decode
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0xFF, 0xFE // Addr Len is not a multiple of 16
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+TEST(OptionDnr6Test, validIpV6Addresses) {
+ // Prepare data to decode
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x30, // Addr Len field value = 48 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ff02::face:b00c
+ 0x00, 0x00, 0x00, 0x00, 0xfa, 0xce, 0xb0, 0x0c,
+ // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ scoped_ptr<OptionDnr6> option;
+ EXPECT_NO_THROW(option.reset(new OptionDnr6(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(0x8001, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ("myhost.example.com.", option->getAdn());
+ EXPECT_EQ(48, option->getAddrLength());
+ const OptionDnr6::AddressContainer& addresses = option->getAddresses();
+ EXPECT_EQ(3, addresses.size());
+ EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText());
+ EXPECT_EQ("ff02::face:b00c", addresses[1].toText());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addresses[2].toText());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+}
+
} // namespace
\ No newline at end of file