/// - next-server = 10.0.0.7
/// - server name = "some-name.example.org"
/// - boot-file-name = "bootfile.efi"
+///
+/// - Configuration 3:
+/// - This configuration provides reservations for big options
+/// server-hostname and boot-file-name value.
+/// - 1 subnet: 192.0.2.0/24
+/// - 1 reservation for this subnet:
+/// - Client's HW address: aa:bb:cc:dd:ee:ff
+/// - option 240 = data
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -data
+/// - tftp-server-name = some-name
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// .example.org
+/// - boot-file-name = bootfile
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// -010203040506070809-010203040506070809-010203040506070809-010203040506070809
+/// .efi
const char* INFORM_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
" ]"
"} ]"
"}",
+
+// Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"next-server\": \"10.0.0.1\","
+ "\"server-hostname\": \"nohost\","
+ "\"boot-file-name\": \"nofile\","
+ "\"option-def\": ["
+ " {"
+ " \"array\": false,"
+ " \"code\": 240,"
+ " \"encapsulate\": \"\","
+ " \"name\": \"my-option\","
+ " \"record-types\": \"string\","
+ " \"space\": \"dhcp\","
+ " \"type\": \"record\""
+ " }"
+ "],"
+ "\"option-data\": ["
+ " {"
+ " \"always-send\": false,"
+ " \"code\": 240,"
+ " \"name\": \"my-option\","
+ " \"csv-format\": true,"
+ " \"data\": \"data"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-data\","
+ " \"space\": \"dhcp\","
+ " \"type\": \"record\""
+ " }"
+ "],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"reservations\": [ "
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"tftp-server-name\": \"some-name"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ ".example.org\","
+ " \"boot-file-name\": \"bootfile"
+ "-090807060504030201-090807060504030201-090807060504030201-090807060504030201"
+ "-090807060504030201-090807060504030201-090807060504030201-090807060504030201"
+ "-090807060504030201-090807060504030201-090807060504030201-090807060504030201"
+ ".efi\""
+ " }"
+ " ]"
+ "} ]"
+ "}",
};
/// @brief Test fixture class for testing DHCPINFORM.
EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
}
+// This test verifies that the server assigns and splits long options within
+// DHCPv4 message.
+TEST_F(InformTest, messageFieldsLongOptions) {
+ // Client has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Message is relayed.
+ client.useRelay();
+ // Set explicit HW address so as it matches the reservation in the
+ // configuration used below.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(INFORM_CONFIGS[2], *client.getServer());
+ // Client requests big option
+ client.requestOption(240);
+ // Client sends DHCPINFORM and should receive reserved fields.
+ ASSERT_NO_THROW(client.doInform());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ Pkt4Ptr resp = client.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Check that the reserved and requested values have been assigned.
+ string expected =
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809"
+ "-010203040506070809-010203040506070809-010203040506070809-010203040506070809";
+ uint32_t count = 0;
+ string value = "";
+ for (auto const& option : resp->options_) {
+ if (option.second->getType() == 240) {
+ value += string(reinterpret_cast<const char*>(&option.second->getData()[0]),
+ option.second->getData().size());
+ }
+ count++;
+ }
+ EXPECT_EQ(count, 2);
+ EXPECT_EQ(value, string("data") + expected + string("-data"));
+ count = 0;
+ value = "";
+ for (auto const& option : resp->options_) {
+ if (option.second->getType() == DHO_TFTP_SERVER_NAME) {
+ value += string(reinterpret_cast<const char*>(&option.second->getData()[0]),
+ option.second->getData().size());
+ }
+ count++;
+ }
+ EXPECT_EQ(count, 2);
+ EXPECT_EQ(value, string("some-name") + expected + string(".example.org"));
+ count = 0;
+ value = "";
+ for (auto const& option : resp->options_) {
+ if (option.second->getType() == DHO_BOOT_FILE_NAME) {
+ value += string(reinterpret_cast<const char*>(&option.second->getData()[0]),
+ option.second->getData().size());
+ }
+ count++;
+ }
+ EXPECT_EQ(count, 2);
+ EXPECT_EQ(value, string("bootfile") + expected + string(".efi"));
+}
+
/// This test verifies that after a client completes its INFORM exchange,
/// appropriate statistics are updated.
TEST_F(InformTest, statisticsInform) {
const OptionDefContainerPtr
LibDHCP::getOptionDefs(const std::string& space) {
- OptionDefContainers::const_iterator container = option_defs_.find(space);
+ auto const& container = option_defs_.find(space);
if (container != option_defs_.end()) {
return (container->second);
}
LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
OptionDefSpaceContainer defs_copy;
std::list<std::string> option_space_names = defs.getOptionSpaceNames();
- for (std::list<std::string>::const_iterator name = option_space_names.begin();
- name != option_space_names.end(); ++name) {
- OptionDefContainerPtr container = defs.getItems(*name);
- for (OptionDefContainer::const_iterator def = container->begin();
- def != container->end(); ++def) {
- OptionDefinitionPtr def_copy(new OptionDefinition(**def));
+ for (auto const& name : option_space_names) {
+ OptionDefContainerPtr container = defs.getItems(name);
+ for (auto const& def : *container) {
+ OptionDefinitionPtr def_copy(new OptionDefinition(*def));
defs_copy.addItem(def_copy);
}
}
void
LibDHCP::packOptions4(isc::util::OutputBuffer& buf,
- const OptionCollection& options,
- bool top /* = false */) {
+ const OptionCollection& options,
+ bool top /* = false */) {
OptionPtr agent;
OptionPtr end;
}
}
- for (OptionCollection::const_iterator it = options.begin();
- it != options.end(); ++it) {
+ for (auto const& option : options) {
// TYPE is already done, RAI and END options must be last.
- switch (it->first) {
+ switch (option.first) {
case DHO_DHCP_MESSAGE_TYPE:
break;
case DHO_DHCP_AGENT_OPTIONS:
- agent = it->second;
+ agent = option.second;
break;
case DHO_END:
- end = it->second;
+ end = option.second;
break;
default:
- it->second->pack(buf);
+ option.second->pack(buf);
break;
}
}
// Add the RAI option if it exists.
if (agent) {
- agent->pack(buf);
+ agent->pack(buf);
}
// And at the end the END option.
if (end) {
- end->pack(buf);
+ end->pack(buf);
+ }
+}
+
+void
+LibDHCP::splitOptions4(OptionCollection& options) {
+ // We need to loop until all options have been split.
+ for (;;) {
+ bool found = false;
+ // Iterate over all options in the container.
+ for (auto const& option : options) {
+ OptionPtr candidate = option.second;
+ OptionCollection& sub_options = candidate->getMutableOptions();
+ // Split suboptions recursively, if any.
+ if (sub_options.size()) {
+ LibDHCP::splitOptions4(sub_options);
+ }
+ uint32_t header_len = candidate->getHeaderLen();
+ // Maximum option buffer size is 255 - header size.
+ uint8_t len = 255 - header_len;
+ // Current option size after split is the sum of the data, the
+ // suboptions and the header size. The header is duplicated in all
+ // new options, but the rest of the data must be serialized.
+ uint32_t size = candidate->len() - header_len;
+ // Only split if data does not fit in the current option.
+ if (size > len) {
+ // Make a copy of the options so we can safely iterate over the
+ // old container.
+ OptionCollection copy = options;
+ // Erase the old option from the new container so that only new
+ // options are present.
+ copy.erase(option.first);
+ uint32_t offset = 0;
+ // Drain the option buffer in multiple new options until all
+ // data is serialized.
+ for (; offset != size;) {
+ // Adjust the data length of the new option if remaining
+ // data is less than the 255 - header size (for the last
+ // option).
+ if (size - offset < len) {
+ len = size - offset;
+ }
+ // Create new option with data starting from offset and
+ // containing truncated length.
+ OptionPtr new_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(candidate->getData()[offset],
+ len)));
+ // If this is the first option, also add (if necessary,
+ // already split) suboptions, if any.
+ if (!offset) {
+ new_option->getMutableOptions() = candidate->getOptions();
+ }
+ // Adjust the offset for remaining data to be written to the
+ // next new option.
+ offset += len;
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), new_option));
+ }
+ // After all new options have been split and added, update the
+ // option container with the new container.
+ options = copy;
+ // Other options might need splitting, so we need to iterate
+ // again until no option needs splitting.
+ found = true;
+ break;
+ }
+ }
+ // No option needs splitting, so we can exit the loop.
+ if (!found) {
+ break;
+ }
}
}
void
LibDHCP::packOptions6(isc::util::OutputBuffer& buf,
const OptionCollection& options) {
- for (OptionCollection::const_iterator it = options.begin();
- it != options.end(); ++it) {
- it->second->pack(buf);
+ for (auto const& option : options) {
+ option.second->pack(buf);
}
}
const isc::dhcp::OptionCollection& options,
bool top = false);
+ /// @brief Split long options in multiple suboptions with the same option
+ /// code (RFC3396).
+ ///
+ /// @param options The option container which needs to be updated with split
+ /// options.
+ static void splitOptions4(isc::dhcp::OptionCollection& options);
+
/// @brief Stores DHCPv6 options in a buffer.
///
/// Stores all options defined in options containers in a on-wire
return(LibDHCP::optionFactory(u, type, buf));
}
-
Option::Option(Universe u, uint16_t type)
:universe_(u), type_(type) {
check();
void
Option::check() const {
- if ( (universe_ != V4) && (universe_ != V6) ) {
+ if ((universe_ != V4) && (universe_ != V6)) {
isc_throw(BadValue, "Invalid universe type specified. "
<< "Only V4 and V6 are allowed.");
}
if (type_ > 255) {
isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
<< "For DHCPv4 allowed type range is 0..255");
- } else if (data_.size() > 255) {
- isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
- /// TODO Larger options can be stored as separate instances
- /// of DHCPv4 options. Clients MUST concatenate them.
- /// Fortunately, there are no such large options used today.
}
}
size_t length = getHeaderLen() + data_.size();
// ... and sum of lengths of all suboptions
- for (OptionCollection::const_iterator it = options_.begin();
- it != options_.end();
- ++it) {
- length += (*it).second->len();
+ for (auto const& option : options_) {
+ length += option.second->len();
}
// note that this is not equal to length field. This value denotes
}
OptionPtr Option::getOption(uint16_t opt_type) const {
- isc::dhcp::OptionCollection::const_iterator x =
- options_.find(opt_type);
- if ( x != options_.end() ) {
- return (*x).second;
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
+ return (x->second);
}
return OptionPtr(); // NULL
}
void
Option::getOptionsCopy(OptionCollection& options_copy) const {
OptionCollection local_options;
- for (OptionCollection::const_iterator it = options_.begin();
- it != options_.end(); ++it) {
- OptionPtr copy = it->second->clone();
- local_options.insert(std::make_pair(it->second->getType(),
- copy));
+ for (auto const& option : options_) {
+ OptionPtr copy = option.second->clone();
+ local_options.insert(std::make_pair(option.second->getType(), copy));
}
// All options copied successfully, so assign them to the output
// parameter.
}
bool Option::delOption(uint16_t opt_type) {
- isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
- if ( x != options_.end() ) {
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
options_.erase(x);
- return true; // delete successful
+ return (true); // delete successful
}
return (false); // option not found, can't delete
}
-
std::string Option::toText(int indent) const {
std::stringstream output;
output << headerToText(indent) << ": ";
if (!options_.empty()) {
output << "," << std::endl << "options:";
- for (OptionCollection::const_iterator opt = options_.begin();
- opt != options_.end(); ++opt) {
- output << std::endl << (*opt).second->toText(indent);
+ for (auto const& opt : options_) {
+ output << std::endl << opt.second->toText(indent);
}
}
/// @brief returns option universe (V4 or V6)
///
/// @return universe type
- Universe getUniverse() const { return universe_; };
+ Universe getUniverse() const { return universe_; };
/// @brief Writes option in wire-format to a buffer.
///
return (options_);
}
+ /// @brief Returns all encapsulated options.
+ ///
+ /// @warning This function returns a reference to the container holding
+ /// encapsulated options, which is valid as long as the object which
+ /// returned it exists.
+ OptionCollection& getMutableOptions() {
+ return (options_);
+ }
+
/// @brief Performs deep copy of suboptions.
///
/// This method calls @ref clone method to deep copy each option.
// write DHCP magic cookie
buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE);
+ // The RFC3396 adds support for long options split over multiple options
+ // using the same code.
+ LibDHCP::splitOptions4(options_);
+
// Call packOptions4() with parameter,"top", true. This invokes
// logic to emit the message type option first.
LibDHCP::packOptions4(buffer_out_, options_, true);
0x01, 0x02, 0x03, 0x00 // Vendor Specific Information continued
};
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, longOption) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (unsigned i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ // Use scoped pointer because it allows to declare the option
+ // in the function scope and initialize it under ASSERT.
+ boost::shared_ptr<OptionCustom> option;
+ // Custom option may throw exception if the provided buffer is
+ // malformed.
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
+ );
+ ASSERT_TRUE(option);
+ isc::util::OutputBuffer buf(0);
+ OptionCollection col;
+ col.insert(std::make_pair(213, option));
+ LibDHCP::splitOptions4(col);
+ LibDHCP::packOptions4(buf, col, true);
+
+ EXPECT_EQ(11, col.size());
+}
+
// This test verifies that pack options for v4 is working correctly.
TEST_F(LibDhcpTest, packOptions4) {