Browse Source

[3082] Add support for ASCII domain name encoding and decoding.

Marcin Siodelski 11 years ago
parent
commit
b2a5975208

+ 52 - 9
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());
+
     }
 }
 

+ 12 - 4
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

+ 147 - 2
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<Option4ClientFqdn> 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<Option4ClientFqdn> 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<Option4ClientFqdn> 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) {