Commit 4650a922 authored by Francis Dupont's avatar Francis Dupont

[5241] Added the function in DHCPv4 and 6 servers

parent ac7850b4
......@@ -1226,21 +1226,37 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
}
Pkt4Ptr query = ex.getQuery();
Pkt4Ptr resp = ex.getResponse();
std::vector<uint8_t> requested_opts;
// try to get the 'Parameter Request List' option which holds the
// codes of requested options.
OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
// If there is no PRL option in the message from the client then
// there is nothing to do.
if (!option_prl) {
return;
// Get the codes of requested options.
if (option_prl) {
requested_opts = option_prl->getValues();
}
// Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
if (!opts) {
continue;
}
// Get persistent options
const OptionContainerPersistIndex& idx = opts->get<2>();
const OptionContainerPersistRange& range = idx.equal_range(true);
for (OptionContainerPersistIndex::const_iterator desc = range.first;
desc != range.second; ++desc) {
// Add the persistent option code to requested options
if (desc->option_) {
uint8_t code = static_cast<uint8_t>(desc->option_->getType());
requested_opts.push_back(code);
}
}
}
Pkt4Ptr resp = ex.getResponse();
// Get the codes of requested options.
const std::vector<uint8_t>& requested_opts = option_prl->getValues();
// For each requested option code get the instance of the option
// to be returned to the client.
for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
......@@ -1288,15 +1304,39 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
}
uint32_t vendor_id = vendor_req->getVendorId();
std::vector<uint8_t> requested_opts;
// Let's try to get ORO within that vendor-option
/// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
/// vendors may have different policies.
OptionUint8ArrayPtr oro =
boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
// Get the list of options that client requested.
if (oro) {
requested_opts = oro->getValues();
}
// Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
if (!opts) {
continue;
}
// Get persistent options
const OptionContainerPersistIndex& idx = opts->get<2>();
const OptionContainerPersistRange& range = idx.equal_range(true);
for (OptionContainerPersistIndex::const_iterator desc = range.first;
desc != range.second; ++desc) {
// Add the persistent option code to requested options
if (desc->option_) {
uint8_t code = static_cast<uint8_t>(desc->option_->getType());
requested_opts.push_back(code);
}
}
}
// Option ORO not found. Don't do anything then.
if (!oro) {
// If there is nothing to add don't do anything then.
if (requested_opts.empty()) {
return;
}
......@@ -1304,8 +1344,6 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
// Get the list of options that client requested.
bool added = false;
const std::vector<uint8_t>& requested_opts = oro->getValues();
for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
code != requested_opts.end(); ++code) {
if (!vendor_rsp->getOption(*code)) {
......
......@@ -2820,7 +2820,7 @@ TEST_F(Dhcp4ParserTest, optionDataSinglePool) {
// Expect a single option with the code equal to 100.
ASSERT_EQ(1, std::distance(range.first, range.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
......@@ -2893,7 +2893,7 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
// Expect a single option with the code equal to 100.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
......@@ -2909,8 +2909,8 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
const OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(23);
OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(23);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
const uint8_t foo2_expected[] = {
0x01
......
......@@ -1451,6 +1451,89 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
EXPECT_EQ("192.0.2.2", addrs[1].toText());
}
// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
// vendor options is parsed correctly and persistent options are actually assigned.
TEST_F(Dhcpv4SrvTest, vendorPersistentOptions) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
NakedDhcpv4Srv srv(0);
ConstElementPtr x;
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
" \"option-data\": [ {"
" \"name\": \"tftp-servers\","
" \"space\": \"vendor-4491\","
" \"code\": 2,"
" \"data\": \"192.0.2.1, 192.0.2.2\","
" \"csv-format\": true,"
" \"persistent\": true"
" }],"
"\"subnet4\": [ { "
" \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000,"
" \"interface\": \"eth0\" "
" } ],"
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
ASSERT_TRUE(x);
comment_ = isc::config::parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
CfgMgr::instance().commit();
boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
// Set the giaddr and hops to non-zero address as if it was relayed.
dis->setGiaddr(IOAddress("192.0.2.1"));
dis->setHops(1);
OptionPtr clientid = generateClientId();
dis->addOption(clientid);
// Set interface. It is required by the server to generate server id.
dis->setIface("eth0");
// Let's add a vendor-option (vendor-id=4491).
OptionPtr vendor(new OptionVendor(Option::V4, 4491));
dis->addOption(vendor);
// Pass it to the server and get an advertise
Pkt4Ptr offer = srv.processDiscover(dis);
// check if we get response at all
ASSERT_TRUE(offer);
// Check if there is a vendor option response
OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_TRUE(tmp);
// The response should be OptionVendor object
boost::shared_ptr<OptionVendor> vendor_resp =
boost::dynamic_pointer_cast<OptionVendor>(tmp);
ASSERT_TRUE(vendor_resp);
OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
ASSERT_TRUE(docsis2);
Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
ASSERT_TRUE(tftp_srvs);
Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
ASSERT_EQ(2, addrs.size());
EXPECT_EQ("192.0.2.1", addrs[0].toText());
EXPECT_EQ("192.0.2.2", addrs[1].toText());
}
// Test checks whether it is possible to use option definitions defined in
// src/lib/dhcp/docsis3_option_defs.h.
TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
......@@ -1952,6 +2035,85 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) {
EXPECT_NE(0, opt->getUint8());
}
// Checks class options have the priority over global persistent options
TEST_F(Dhcpv4SrvTest, classGlobalPersistency) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
NakedDhcpv4Srv srv(0);
// A global ip-forwarding option is set in the response.
// The router class matches incoming packets with foo in a host-name
// option (code 12) and sets an ip-forwarding option in the response.
// Note the persistency flag follows a "OR" semantic so to set
// it to false (or to leave the default) has no effect.
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ] }, "
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ "
"{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
" \"subnet\": \"192.0.2.0/24\" } ], "
"\"option-data\": ["
" { \"name\": \"ip-forwarding\", "
" \"data\": \"false\", "
" \"persistent\": true } ], "
"\"client-classes\": [ "
"{ \"name\": \"router\","
" \"option-data\": ["
" { \"name\": \"ip-forwarding\", "
" \"data\": \"true\", "
" \"persistent\": false } ], "
" \"test\": \"option[12].text == 'foo'\" } ] }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
ASSERT_TRUE(status);
comment_ = config::parseAnswer(rcode_, status);
ASSERT_EQ(0, rcode_);
CfgMgr::instance().commit();
// Create a packet with enough to select the subnet and go through
// the DISCOVER processing
Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
query->setRemoteAddr(IOAddress("192.0.2.1"));
OptionPtr clientid = generateClientId();
query->addOption(clientid);
query->setIface("eth1");
// Do not add a PRL
OptionPtr prl = query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST);
EXPECT_FALSE(prl);
// Create and add a host-name option to the query
OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
ASSERT_TRUE(hostname);
query->addOption(hostname);
// Classify the packet
srv.classifyPacket(query);
// The packet should be in the router class
EXPECT_TRUE(query->inClass("router"));
// Process the query
Pkt4Ptr response = srv.processDiscover(query);
// Processing should add an ip-forwarding option
OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
ASSERT_TRUE(opt);
ASSERT_GT(opt->len(), opt->getHeaderLen());
// Classification sets the value to true/1, global to false/0
// Here class has the priority
EXPECT_NE(0, opt->getUint8());
}
// Checks if the client-class field is indeed used for subnet selection.
// Note that packet classification is already checked in Dhcpv4SrvTest
// .*Classification above.
......
......@@ -918,20 +918,39 @@ void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
const CfgOptionList& co_list) {
// Unlikely short cut
if (co_list.empty()) {
return;
}
std::vector<uint16_t> requested_opts;
// Client requests some options using ORO option. Try to
// get this option from client's message.
boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
(question->getOption(D6O_ORO));
// If there is no ORO option, there is nothing more to do.
if (!option_oro) {
return;
}
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
if (option_oro) {
requested_opts = option_oro->getValues();
}
// Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
const OptionContainerPtr& opts = (*copts)->getAll(DHCP6_OPTION_SPACE);
if (!opts) {
continue;
}
// Get persistent options
const OptionContainerPersistIndex& idx = opts->get<2>();
const OptionContainerPersistRange& range = idx.equal_range(true);
for (OptionContainerPersistIndex::const_iterator desc = range.first;
desc != range.second; ++desc) {
// Add the persistent option code to requested options
requested_opts.push_back(desc->option_->getType());
}
}
BOOST_FOREACH(uint16_t opt, requested_opts) {
// Iterate on the configured option list
......@@ -969,24 +988,44 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
return;
}
uint32_t vendor_id = vendor_req->getVendorId();
std::vector<uint16_t> requested_opts;
// Let's try to get ORO within that vendor-option
/// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
/// may have different policies.
boost::shared_ptr<OptionUint16Array> oro =
boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO));
if (oro) {
requested_opts = oro->getValues();
}
// Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
if (!opts) {
continue;
}
// Get persistent options
const OptionContainerPersistIndex& idx = opts->get<2>();
const OptionContainerPersistRange& range = idx.equal_range(true);
for (OptionContainerPersistIndex::const_iterator desc = range.first;
desc != range.second; ++desc) {
// Add the persistent option code to requested options
requested_opts.push_back(desc->option_->getType());
}
}
// Option ORO not found. Don't do anything then.
if (!oro) {
// If there is nothing to add don't do anything then.
if (requested_opts.empty()) {
return;
}
uint32_t vendor_id = vendor_req->getVendorId();
boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
// Get the list of options that client requested.
bool added = false;
const std::vector<uint16_t>& requested_opts = oro->getValues();
BOOST_FOREACH(uint16_t opt, requested_opts) {
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
......
// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -518,6 +518,74 @@ TEST_F(ClassifyTest, classGlobalPriority) {
EXPECT_NE(0, opt->getUint8());
}
// Checks class options have the priority over global persistent options
TEST_F(ClassifyTest, classGlobalPersistency) {
IfaceMgrTestConfig test_config(true);
NakedDhcpv6Srv srv(0);
// Subnet sets an ipv6-forwarding option in the response.
// The router class matches incoming packets with foo in a host-name
// option (code 1234) and sets an ipv6-forwarding option in the response.
// Note the persistency flag follows a "OR" semantic so to set
// it to false (or to leave the default) has no effect.
std::string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ] }, "
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"option-def\": [ "
"{ \"name\": \"host-name\","
" \"code\": 1234,"
" \"type\": \"string\" },"
"{ \"name\": \"ipv6-forwarding\","
" \"code\": 2345,"
" \"type\": \"boolean\" }],"
"\"option-data\": ["
" { \"name\": \"ipv6-forwarding\", "
" \"data\": \"false\", "
" \"persistent\": true } ], "
"\"subnet6\": [ "
"{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
" \"subnet\": \"2001:db8:1::/48\", "
" \"interface\": \"eth1\", "
" \"option-data\": ["
" { \"name\": \"ipv6-forwarding\", "
" \"data\": \"false\", "
" \"persistent\": false } ] } ] }";
ASSERT_NO_THROW(configure(config));
// Create a packet with enough to select the subnet and go through
// the SOLICIT processing
Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
query->setRemoteAddr(IOAddress("fe80::abcd"));
OptionPtr clientid = generateClientId();
query->addOption(clientid);
query->setIface("eth1");
query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
// Do not add an ORO.
OptionPtr oro = query->getOption(D6O_ORO);
EXPECT_FALSE(oro);
// Create and add a host-name option to the query
OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
ASSERT_TRUE(hostname);
query->addOption(hostname);
// Process the query
Pkt6Ptr response = srv.processSolicit(query);
// Processing should add an ip-forwarding option
OptionPtr opt = response->getOption(2345);
ASSERT_TRUE(opt);
ASSERT_GT(opt->len(), opt->getHeaderLen());
// Global sets the value to true/1, subnet to false/0
// Here subnet has the priority
EXPECT_EQ(0, opt->getUint8());
}
// Checks if the client-class field is indeed used for subnet selection.
// Note that packet classification is already checked in ClassifyTest
// .*Classification above.
......
......@@ -1666,6 +1666,78 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
}
// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
// vendor options is parsed correctly and the persistent options are actually assigned.
TEST_F(Dhcpv6SrvTest, vendorPersistentOptions) {
IfaceMgrTestConfig test_config(true);
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
" \"option-def\": [ {"
" \"name\": \"config-file\","
" \"code\": 33,"
" \"type\": \"string\","
" \"space\": \"vendor-4491\""
" } ],"
" \"option-data\": [ {"
" \"name\": \"config-file\","
" \"space\": \"vendor-4491\","
" \"data\": \"normal_erouter_v6.cm\","
" \"persistent\": true"
" }],"
"\"subnet6\": [ { "
" \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"renew-timer\": 1000, "
" \"rebind-timer\": 1000, "
" \"preferred-lifetime\": 3000,"
" \"valid-lifetime\": 4000,"
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000 }";
ASSERT_NO_THROW(configure(config));
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
sol->setRemoteAddr(IOAddress("fe80::abcd"));
sol->setIface("eth0");
sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
OptionPtr clientid = generateClientId();
sol->addOption(clientid);
// Let's add a vendor-option (vendor-id=4491).
OptionPtr vendor(new OptionVendor(Option::V6, 4491));
sol->addOption(vendor);
// Pass it to the server and get an advertise
Pkt6Ptr adv = srv_.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
// Check if there is vendor option response
OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
ASSERT_TRUE(tmp);
// The response should be OptionVendor object
boost::shared_ptr<OptionVendor> vendor_resp =
boost::dynamic_pointer_cast<OptionVendor>(tmp);
ASSERT_TRUE(vendor_resp);
OptionPtr docsis33 = vendor_resp->getOption(33);
ASSERT_TRUE(docsis33);
OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
ASSERT_TRUE(config_file);
EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
}
// Test checks whether it is possible to use option definitions defined in
// src/lib/dhcp/docsis3_option_defs.h.
TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
......
......@@ -184,6 +184,11 @@ typedef std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
/// Type of the index #2 - option persistency flag.
typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
/// Pair of iterators to represent the range of options having the
/// same persistency flag. The first element in this pair represents
/// the beginning of the range, the second element represents the end.
typedef std::pair<OptionContainerPersistIndex::const_iterator,
OptionContainerPersistIndex::const_iterator> OptionContainerPersistRange;
/// @brief Represents option data configuration for the DHCP server.
///
......
......@@ -449,9 +449,7 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
// For each code we should get two instances of options->
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(code);
OptionContainerTypeRange range = idx.equal_range(code);
// Distance between iterators indicates how many options
// have been returned for the particular code.
ASSERT_EQ(2, distance(range.first, range.second));
......@@ -465,9 +463,7 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
// Let's try to find some non-exiting option.
const uint16_t non_existing_code = 150;
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(non_existing_code);
OptionContainerTypeRange range = idx.equal_range(non_existing_code);
// Empty set is expected.
EXPECT_EQ(0, distance(range.first, range.second));
}
......@@ -501,17 +497,13 @@ TEST(Subnet6Test, addPersistentOption) {
OptionContainerPersistIndex& idx = options->get<2>();
// Get all persistent options->
std::pair<OptionContainerPersistIndex::const_iterator,
OptionContainerPersistIndex::const_iterator> range_persistent =
idx.equal_range(true);
// 3 out of 10 options have been flagged persistent.
OptionContainerPersistRange range_persistent = idx.equal_range(true);
// 7 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
// Get all non-persistent options->
std::pair<OptionContainerPersistIndex::const_iterator,
OptionContainerPersistIndex::const_iterator> range_non_persistent =
idx.equal_range(false);
// 7 out of 10 options have been flagged persistent.
OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
// 3 out of 10 options have been flagged not persistent.
ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
}
......
......@@ -944,9 +944,7 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
// For each code we should get two instances of options->
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator