<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>
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) {
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);
}
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);
}
#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>
return (haveType(OPT_TUPLE_TYPE) && getArrayType());
}
+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".
return (option);
}
+OptionPtr
+OptionDefinition::factoryFqdnList(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
+
+ const std::vector<uint8_t> data(begin, end);
+ 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,
} else {
if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {
return (OptionPtr(new Option4ClientFqdn(begin, end)));
+ } else if ((getCode() == DHO_DOMAIN_SEARCH) &&
+ haveCompressedFqdnListFormat()) {
+ return (factoryFqdnList(Option::V4, begin, end));
} else if ((getCode() == DHO_VIVCO_SUBOPTIONS) &&
haveVendorClass4Format()) {
// V-I Vendor Class (option code 124).
/// @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
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
{ "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
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[] = {
}
}
+// This test checks handling of compressed 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
+ };
+ // 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));
+
+ 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));
+
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ compressed_buf.begin(),
+ compressed_buf.end()));
+ ASSERT_TRUE(option);
+ 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));
+
+ 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));
+
+ EXPECT_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ bad_buf.begin(),
+ bad_buf.end()),
+ InvalidOptionValue);
+}
+
// tests whether v6 vendor-class option can be parsed properly.
TEST_F(LibDhcpTest, vendorClass6) {