From: Marcin Siodelski Date: Fri, 9 Aug 2013 17:29:39 +0000 (+0200) Subject: [3082] Add support for ASCII domain name encoding and decoding. X-Git-Tag: bind10-1.2.0beta1-release~259^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b2a59752080f1aa7dbcfd677c5bfb8f65239c4c1;p=thirdparty%2Fkea.git [3082] Add support for ASCII domain name encoding and decoding. --- diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc index d705dc6d43..3e797d540e 100644 --- a/src/lib/dhcp/option4_client_fqdn.cc +++ b/src/lib/dhcp/option4_client_fqdn.cc @@ -51,6 +51,13 @@ public: void parseWireData(OptionBufferConstIter first, OptionBufferConstIter last); + void parseCanonicalDomainName(OptionBufferConstIter first, + OptionBufferConstIter last); + + void + parseASCIIDomainName(OptionBufferConstIter first, + OptionBufferConstIter last); + }; Option4ClientFqdnImpl:: @@ -176,6 +183,24 @@ Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first, rcode1_ = Option4ClientFqdn::Rcode(*(first++)); rcode2_ = Option4ClientFqdn::Rcode(*(first++)); + try { + if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) { + parseCanonicalDomainName(first, last); + + } else { + parseASCIIDomainName(first, last); + + } + } catch (const Exception& ex) { + isc_throw(InvalidOption4ClientFqdnDomainName, + "failed to parse the domain-name in DHCPv4 Client FQDN" + << " Option: " << ex.what()); + } +} + +void +Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first, + OptionBufferConstIter last) { // Parse domain-name if any. if (std::distance(first, last) > 0) { // The FQDN may comprise a partial domain-name. In this case it lacks @@ -204,7 +229,18 @@ Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first, } } - Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode) +void +Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first, + OptionBufferConstIter last) { + if (std::distance(first, last) > 0) { + std::string domain_name(first, last); + domain_name_.reset(new isc::dns::Name(domain_name)); + domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ? + Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL; + } +} + +Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode) : Option(Option::V4, DHO_FQDN), impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) { } @@ -307,15 +343,22 @@ Option4ClientFqdn::getDomainName() const { void Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const { - // Domain name, encoded as a set of labels. - isc::dns::LabelSequence labels(*impl_->domain_name_); - if (labels.getDataLength() > 0) { - size_t read_len = 0; - const uint8_t* data = labels.getData(&read_len); - if (impl_->domain_name_type_ == PARTIAL) { - --read_len; + if (getFlag(FLAG_E)) { + // Domain name, encoded as a set of labels. + isc::dns::LabelSequence labels(*impl_->domain_name_); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + if (impl_->domain_name_type_ == PARTIAL) { + --read_len; + } + buf.writeData(data, read_len); } - buf.writeData(data, read_len); + + } else { + std::string domain_name = impl_->domain_name_->toText(); + buf.writeData(&domain_name[0], domain_name.size()); + } } diff --git a/src/lib/dhcp/option4_client_fqdn.h b/src/lib/dhcp/option4_client_fqdn.h index b02736df88..980706a436 100644 --- a/src/lib/dhcp/option4_client_fqdn.h +++ b/src/lib/dhcp/option4_client_fqdn.h @@ -79,10 +79,18 @@ class Option4ClientFqdnImpl; /// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully /// qualified or partial. Partial domain names are encoded similar to the /// fully qualified domain names, except that they lack terminating zero -/// at the end of their wire representation. It is also accepted to create an -/// instance of this option which has empty domain-name. Clients use empty -/// domain-names to indicate that server should generate complete fully -/// qualified domain-name. +/// at the end of their wire representation (or dot in case of ASCII encoding). +/// It is also accepted to create an/ instance of this option which has empty +/// domain-name. Clients use empty domain-names to indicate that server should +/// generate complete fully qualified domain-name. +/// +/// @warning: The RFC4702 section 2.3.1 states that the clients and servers +/// should use character sets specified in RFC952, section 2.1. This class doesn't +/// detect the character set violation for ASCII encoded domain-name. This could +/// be implemented in the future but it is not important for two reasons: +/// - ASCII encoding is deprecated +/// - clients SHOULD obey restrictions but if they don't server may still +/// process the option /// /// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that /// server sets them to 255. This class allows to set the value for these diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc index 002cc0e424..b1eb1745c2 100644 --- a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc +++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc @@ -143,8 +143,10 @@ TEST(Option4ClientFqdnTest, copyConstruct) { EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType()); } -// This test verifies that the option in the on-wire format is parsed correctly. +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the canonical format is parsed correctly. TEST(Option4ClientFqdnTest, constructFromWire) { + // The E flag sets the domain-name format to canonical. const uint8_t in_data[] = { FLAG_S | FLAG_E, // flags 0, // RCODE1 @@ -171,6 +173,38 @@ TEST(Option4ClientFqdnTest, constructFromWire) { EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); } +// This test verifies that the option in the on-wire format with the domain-name +// encoded in the ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWireASCII) { + // The E flag is set to zero which indicates that the domain name + // is encoded in the ASCII format. The "dot" character at the end + // indicates that the domain-name is fully qualified. + const uint8_t in_data[] = { + FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example.com.", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType()); +} + // This test verifies that truncated option is rejected. TEST(Option4ClientFqdnTest, constructFromWireTruncated) { // Empty buffer is invalid. It should be at least one octet long. @@ -186,8 +220,48 @@ TEST(Option4ClientFqdnTest, constructFromWireTruncated) { EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end())); } +// This test verifies that exception is thrown when invalid domain-name +// in canonical format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidName) { + const uint8_t in_data[] = { + FLAG_S | FLAG_E, // flags + 0, // RCODE1 + 0, // RCODE2 + 6, 109, 121, 104, 111, 115, 116, // myhost. + 7, 101, 120, 97, 109, 112, 108, 101, // example. + 5, 99, 111, 109, 0 // com. (invalid label length 5) + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW( + Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4ClientFqdnDomainName + ); +} + +// This test verifies that exception is thrown when invalid domain-name +// in ASCII format is carried in the option. +TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) { + const uint8_t in_data[] = { + FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!) + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + EXPECT_THROW( + Option4ClientFqdn(in_buf.begin(), in_buf.end()), + InvalidOption4ClientFqdnDomainName + ); +} + // This test verifies that the option in the on-wire format with partial -// domain-name is parsed correctly. +// domain-name encoded in canonical format is parsed correctly. TEST(Option4ClientFqdnTest, constructFromWirePartial) { const uint8_t in_data[] = { FLAG_N | FLAG_E, // flags @@ -213,6 +287,35 @@ TEST(Option4ClientFqdnTest, constructFromWirePartial) { EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); } +// This test verifies that the option in the on-wire format with partial +// domain-name encoded in ASCII format is parsed correctly. +TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) { + // There is no "dot" character at the end, so the domain-name is partial. + const uint8_t in_data[] = { + FLAG_N, // flags + 255, // RCODE1 + 255, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101 // example + }; + size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]); + OptionBuffer in_buf(in_data, in_data + in_data_size); + + // Create option instance. Check that constructor doesn't throw. + boost::scoped_ptr option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end())) + ); + ASSERT_TRUE(option); + + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E)); + EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O)); + EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N)); + EXPECT_EQ("myhost.example", option->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); +} + // This test verifies that the option in the on-wire format with empty // domain-name is parsed correctly. TEST(Option4ClientFqdnTest, constructFromWireEmpty) { @@ -356,6 +459,14 @@ TEST(Option4ClientFqdnTest, constructInvalidName) { EXPECT_THROW(Option4ClientFqdn(FLAG_E, Option4ClientFqdn::RCODE_CLIENT(), "my...host.example.com"), InvalidOption4ClientFqdnDomainName); + + // Do the same test for the domain-name in ASCII format. + ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")); + + EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(), + "my...host.example.com"), + InvalidOption4ClientFqdnDomainName); } // This test verifies that getFlag throws an exception if flag value other @@ -560,6 +671,40 @@ TEST(Option4ClientFqdnTest, pack) { EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); } +TEST(Option4ClientFqdnTest, packASCII) { + // Create option instance. Check that constructor doesn't throw. + const uint8_t flags = FLAG_S; + boost::scoped_ptr option; + ASSERT_NO_THROW( + option.reset(new Option4ClientFqdn(flags, + Option4ClientFqdn::RCODE_CLIENT(), + "myhost.example.com")) + ); + ASSERT_TRUE(option); + + // Prepare on-wire format of the option. + isc::util::OutputBuffer buf(10); + ASSERT_NO_THROW(option->pack(buf)); + + // Prepare reference data. + const uint8_t ref_data[] = { + 81, 23, // header + FLAG_S, // flags + 0, // RCODE1 + 0, // RCODE2 + 109, 121, 104, 111, 115, 116, 46, // myhost. + 101, 120, 97, 109, 112, 108, 101, 46, // example. + 99, 111, 109, 46 // com. + }; + size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]); + + // Check if the buffer has the same length as the reference data, + // so as they can be compared directly. + ASSERT_EQ(ref_data_size, buf.getLength()); + EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength())); + +} + // This test verifies on-wire format of the option with partial domain name // is correctly created. TEST(Option4ClientFqdnTest, packPartial) {