Browse Source

[master] Merged trac5087 (domain-search CSV)

Francis Dupont 8 years ago
parent
commit
5828a0c2af

+ 8 - 0
doc/examples/kea4/multiple-options.json

@@ -84,6 +84,14 @@
              "code": 15,
              "data": "example.org"
          },
+             // Domain search is also a popular option. It tells the client to
+             // attempt to resolve names within those specificed domains. For
+             // example, name "foo" would be attempted to be resolved as
+             // foo.mydomain.example.com and if it fails, then as foo.example.com
+         {
+             "name": "domain-search",
+             "data": "mydomain.example.com, example.com"
+         },
             // String options that have a comma in their values need to have
             // it escaped (i.e. each comma is predeced by two backslashes).
             // That's because commas are reserved for separating fields in

+ 1 - 1
doc/guide/dhcp4-srv.xml

@@ -1245,7 +1245,7 @@ It is merely echoed by the server
 <row><entry>client-ndi</entry><entry>94</entry><entry>record (uint8, uint8, uint8)</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>uuid-guid</entry><entry>97</entry><entry>record (uint8, binary)</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>subnet-selection</entry><entry>118</entry><entry>ipv4-address</entry><entry>false</entry><entry>false</entry></row>
-<row><entry>domain-search</entry><entry>119</entry><entry>binary</entry><entry>false</entry><entry>false</entry></row>
+<row><entry>domain-search</entry><entry>119</entry><entry>fqdn</entry><entry>true</entry><entry>false</entry></row>
 <row><entry>vivco-suboptions</entry><entry>124</entry><entry>binary</entry><entry>false</entry><entry>false</entry></row>
 <row><entry>vivso-suboptions</entry><entry>125</entry><entry>binary</entry><entry>false</entry><entry>false</entry></row>
           </tbody>

+ 20 - 4
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -2930,6 +2930,22 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
+// The goal of this test is to verify that the domain-search option
+// can be set using domain names
+TEST_F(Dhcp4ParserTest, domainSearchOption) {
+    // Create configuration.
+    std::map<std::string, std::string> params;
+    params["name"] = "domain-search";
+    params["space"] = DHCP4_OPTION_SPACE;
+    params["code"] = "119"; // DHO_DOMAIN_SEARCH
+    params["data"] = "mydomain.example.com, example.com";
+    params["csv-format"] = "true";
+
+    std::string config = createConfigWithOption(params);
+    EXPECT_TRUE(executeConfiguration(config, "parse configuration with a"
+                                     " domain-search option"));
+}
+
 // The goal of this test is to verify that the standard option can
 // be configured to encapsulate multiple other options.
 TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
@@ -4748,7 +4764,7 @@ TEST_F(Dhcp4ParserTest, invalidPoolRange) {
 
     EXPECT_EQ(1, rcode);
     string expected = "Failed to create pool defined by: "
-	"192.0.2.1-19.2.0.200 (<string>:6:26)";
+        "192.0.2.1-19.2.0.200 (<string>:6:26)";
     EXPECT_EQ(expected, text);
 }
 
@@ -4778,9 +4794,9 @@ TEST_F(Dhcp4ParserTest, outsideSubnetPool) {
 
     EXPECT_EQ(1, rcode);
     string expected = "subnet configuration failed: "
-	"a pool of type V4, with the following address range: "
-	"192.0.2.1-192.0.2.100 does not match the prefix of a subnet: "
-	"10.0.2.0/24 to which it is being added (<string>:5:14)";
+        "a pool of type V4, with the following address range: "
+        "192.0.2.1-192.0.2.100 does not match the prefix of a subnet: "
+        "10.0.2.0/24 to which it is being added (<string>:5:14)";
     EXPECT_EQ(expected, text);
 }
 

+ 40 - 0
src/lib/dhcp/option_definition.cc

@@ -25,6 +25,8 @@
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_vendor_class.h>
 #include <util/encode/hex.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
 #include <util/strutil.h>
 #include <boost/algorithm/string/classification.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -445,6 +447,11 @@ OptionDefinition::haveOpaqueDataTuplesFormat() const {
 }
 
 bool
+OptionDefinition::haveCompressedFqdnListFormat() const {
+    return (haveType(OPT_FQDN_TYPE) && getArrayType());
+}
+
+bool
 OptionDefinition::convertToBool(const std::string& value_str) const {
     // Case-insensitive check that the input is one of: "true" or "false".
     if (boost::iequals(value_str, "true")) {
@@ -774,6 +781,37 @@ OptionDefinition::factoryOpaqueDataTuples(Option::Universe u,
 }
 
 OptionPtr
+OptionDefinition::factoryFqdnList(Option::Universe u,
+                                  OptionBufferConstIter begin,
+                                  OptionBufferConstIter end) const {
+    
+    const std::vector<uint8_t> data(begin, end);
+    if (data.empty()) {
+        isc_throw(InvalidOptionValue, "FQDN list option has invalid length of 0");
+    }
+    InputBuffer in_buf(static_cast<const void*>(&data[0]), data.size());
+    std::vector<uint8_t> out_buf;
+    out_buf.reserve(data.size());
+    while (in_buf.getPosition() < in_buf.getLength()) {
+        // Reuse readFqdn and writeFqdn code but on the whole buffer
+        // so the DNS name code handles compression for us.
+        try {
+            isc::dns::Name name(in_buf);
+            isc::dns::LabelSequence labels(name);
+            if (labels.getDataLength() > 0) {
+                size_t read_len = 0;
+                const uint8_t* label = labels.getData(&read_len);
+                out_buf.insert(out_buf.end(), label, label + read_len);
+            }
+        } catch (const isc::Exception& ex) {
+            isc_throw(InvalidOptionValue, ex.what());
+        }
+    }
+    return OptionPtr(new OptionCustom(*this, u,
+                                      out_buf.begin(), out_buf.end()));
+}
+
+OptionPtr
 OptionDefinition::factorySpecialFormatOption(Option::Universe u,
                                              OptionBufferConstIter begin,
                                              OptionBufferConstIter end) const {
@@ -820,6 +858,8 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
     } else {
         if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {
             return (OptionPtr(new Option4ClientFqdn(begin, end)));
+        } else if (haveCompressedFqdnListFormat()) {
+            return (factoryFqdnList(Option::V4, begin, end));
         } else if ((getCode() == DHO_VIVCO_SUBOPTIONS) &&
                    haveVendorClass4Format()) {
             // V-I Vendor Class (option code 124).

+ 16 - 0
src/lib/dhcp/option_definition.h

@@ -374,6 +374,9 @@ public:
     /// @return true if option has the format of OpaqueDataTuples type options.
     bool haveOpaqueDataTuplesFormat() const;
 
+    /// @brief Check if the option has format of CompressedFqdnList options.
+    bool haveCompressedFqdnListFormat() const;
+
     /// @brief Option factory.
     ///
     /// This function creates an instance of DHCP option using
@@ -578,6 +581,19 @@ public:
 
 private:
 
+    /// @brief Factory function to create option with a compressed FQDN list.
+    ///
+    /// @param u universe (V4 or V6).
+    /// @param type option type.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
+    ///
+    /// @return instance of the DHCP option where FQDNs are uncompressed.
+    /// @throw InvalidOptionValue if data for the option is invalid.
+    OptionPtr factoryFqdnList(Option::Universe u,
+                              OptionBufferConstIter begin,
+                              OptionBufferConstIter end) const;
+
     /// @brief Creates an instance of an option having special format.
     ///
     /// The option with special formats are encapsulated by the dedicated

+ 1 - 6
src/lib/dhcp/std_option_defs.h

@@ -192,12 +192,7 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
     { "uuid-guid", DHO_UUID_GUID, OPT_RECORD_TYPE, false, RECORD_DEF(UUID_GUID_RECORDS), "" },
     { "subnet-selection", DHO_SUBNET_SELECTION,
       OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
-    // The following options need a special encoding of data
-    // being carried by them. Therefore, there is no way they can
-    // be handled by OptionCustom. We may need to implement
-    // dedicated classes to handle them. Until that happens
-    // let's treat them as 'binary' options.
-    { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "domain-search", DHO_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
     { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, OPT_RECORD_TYPE,
       false, RECORD_DEF(VIVCO_RECORDS), "" },
     // Vendor-Identifying Vendor Specific Information option payload begins with a

+ 132 - 2
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -1319,8 +1319,21 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
     LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17,
                                     typeid(OptionCustom));
 
-    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, begin, end,
-                                    typeid(Option));
+    // Prepare buffer holding an array of FQDNs.
+    const char fqdn_data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+    // Initialize a vector with the FQDN data.
+    std::vector<uint8_t> fqdn_buf(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+                                    fqdn_buf.end(), typeid(OptionCustom));
 
     // V-I Vendor option requires specially crafted data.
     const char vivco_data[] = {
@@ -1654,6 +1667,123 @@ TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
     }
 }
 
+// This test checks handling of uncompressed FQDN list.
+TEST_F(LibDhcpTest, fqdnList) {
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                    DHO_DOMAIN_SEARCH);
+    ASSERT_TRUE(def);
+
+    // Prepare buffer holding an array of FQDNs.
+    const uint8_t fqdn[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+    /* This size is used later so protect ourselves against changes */
+    static_assert(sizeof(fqdn) == 40);
+    // Initialize a vector with the FQDN data.
+    std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));
+
+    OptionPtr option;
+    ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+                                                DHO_DOMAIN_SEARCH,
+                                                fqdn_buf.begin(),
+                                                fqdn_buf.end()));
+    ASSERT_TRUE(option);
+    OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+    ASSERT_TRUE(names);
+    EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen());
+    ASSERT_EQ(3, names->getDataFieldsNum());
+    EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+    EXPECT_EQ("example.com.", names->readFqdn(1));
+    EXPECT_EQ("com.", names->readFqdn(2));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+                                    fqdn_buf.end(), typeid(OptionCustom));
+}
+
+// This test checks handling of compressed FQDN list.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListCompressed) {
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                    DHO_DOMAIN_SEARCH);
+    ASSERT_TRUE(def);
+
+    const uint8_t compressed[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        192, 9,                                   // pointer to example.com
+        192, 17                                   // pointer to com
+    };
+    std::vector<uint8_t> compressed_buf(compressed,
+                                        compressed + sizeof(compressed));
+    OptionPtr option;
+    ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+                                                DHO_DOMAIN_SEARCH,
+                                                compressed_buf.begin(),
+                                                compressed_buf.end()));
+    ASSERT_TRUE(option);
+    OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+    ASSERT_TRUE(names);
+    /* Use the uncompress length here (cf fqdnList) */
+    EXPECT_EQ(40, names->len() - names->getHeaderLen());
+    ASSERT_EQ(3, names->getDataFieldsNum());
+    EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+    EXPECT_EQ("example.com.", names->readFqdn(1));
+    EXPECT_EQ("com.", names->readFqdn(2));
+}
+
+// Check that incorrect FQDN list compression is rejected.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListBad) {
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                    DHO_DOMAIN_SEARCH);
+    ASSERT_TRUE(def);
+
+    const uint8_t bad[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        192, 80,                                  // too big/forward pointer
+        192, 11                                   // pointer to com
+    };
+    std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad));
+
+    OptionPtr option;
+    EXPECT_THROW(option = def->optionFactory(Option::V4,
+                                             DHO_DOMAIN_SEARCH,
+                                             bad_buf.begin(),
+                                             bad_buf.end()),
+                 InvalidOptionValue);
+}
+
+// Check that empty (truncated) option is rejected.
+TEST_F(LibDhcpTest, fqdnListTrunc) {
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+                                                    DHO_DOMAIN_SEARCH);
+    ASSERT_TRUE(def);
+
+    std::vector<uint8_t> empty;
+
+    OptionPtr option;
+    EXPECT_THROW(option = def->optionFactory(Option::V4,
+                                             DHO_DOMAIN_SEARCH,
+                                             empty.begin(),
+                                             empty.end()),
+                 InvalidOptionValue);
+}
+
 // tests whether v6 vendor-class option can be parsed properly.
 TEST_F(LibDhcpTest, vendorClass6) {