strings to avoid issues with creation of the database
when MySQL server operates in ANSI_QUOTES mode.
+ - Cristian Secareanu, Qualitance
+ 2016-10: Support for IPv6 prefix and PDEXCLUDE option
+
+ - Andrei Pavel, Qualitance
+ 2016-10: Support for DHCPv6 options defined in RFC6603 and RFC7598
+
Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
Boost (http://www.boost.org/) library for almost everything, and can use Botan
(http://botan.randombit.net/) or OpenSSL (https://www.openssl.org/) for
nobase_dist_doc_DATA += examples/kea6/reservations.json
nobase_dist_doc_DATA += examples/kea6/several-subnets.json
nobase_dist_doc_DATA += examples/kea6/simple.json
+nobase_dist_doc_DATA += examples/kea6/softwire46.json
nobase_dist_doc_DATA += examples/kea6/stateless.json
devel:
--- /dev/null
+# This is an example configuration file for DHCPv6 server in Kea.
+# It demonstrates how user can specify values for Softwire options
+# defined in RFC 7598.
+
+{ "Dhcp6":
+
+{
+# Kea is told to listen on ethX interface only.
+ "interfaces-config": {
+ "interfaces": [ "ethX" ]
+ },
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+ "lease-database": {
+ "type": "memfile"
+ },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+ "preferred-lifetime": 3000,
+ "valid-lifetime": 4000,
+ "renew-timer": 1000,
+ "rebind-timer": 2000,
+
+# The following list defines subnets. Each subnet consists of at
+# least subnet and pool entries.
+ "subnet6": [
+ {
+ "pools": [ { "pool": "2001:db8:1::/80" } ],
+ "subnet": "2001:db8:1::/64",
+ "interface": "ethX",
+# Include MAP-E Container option for hosts connected to this subnet.
+ "option-data": [
+ {
+ "name": "s46-cont-mape"
+ }
+ ],
+# Send host specific softwire options.
+ "reservations": [
+ {
+ "duid": "01:02:03:04:05:06:07:08:09:0A",
+ "option-data": [
+# These two options will be included in the MAP-E Container
+ {
+ "space": "s46-cont-mape-options",
+ "name": "s46-rule",
+ "data": "1, 0, 24, 192.0.2.0, 2001:db8:1::/64"
+ },
+ {
+ "space": "s46-cont-mape-options",
+ "name": "s46-br",
+ "data": "2001:db8:cafe::1"
+ },
+# This option will be included in the S46 Rule option.
+ {
+ "space": "s46-rule-options",
+ "name": "s46-portparams",
+ "data": "0, 3/4"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+ "loggers": [
+ {
+ "name": "kea-dhcp6",
+ "output_options": [
+ {
+ "output": "/var/log/kea-debug.log"
+ }
+ ],
+ "debuglevel": 99,
+ "severity": "DEBUG"
+ }
+ ]
+}
+
+}
+
</section>
</section>
-<section id="hosts-storage4">
+<section id="hosts4-storage">
<title>Hosts Storage</title>
<para>Kea is also able to store information about host reservations in the
database. The hosts database configuration uses the same syntax as the lease
<row><entry>fqdn</entry><entry>Fully qualified domain name (e.g. www.example.com)</entry></row>
<row><entry>ipv4-address</entry><entry>IPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1)</entry></row>
<row><entry>ipv6-address</entry><entry>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</entry></row>
+ <row><entry>ipv6-prefix</entry><entry>IPv6 prefix and prefix length specified using CIDR notation, e.g. 2001:db8:1::/64. This data type is used to represent an 8-bit field conveying a prefix length and the variable length prefix value</entry></row>
+ <row><entry>psid</entry><entry>PSID and PSID length separated by a slash, e.g. 3/4 specifies PSID=3 and PSID length=4. In the wire format it is represented by an 8-bit field carrying PSID length (in this case equal to 4) and the 16-bits long PSID value field (in this case equal to "0011000000000000b" using binary notation). Allowed values for a PSID length are 0 to 16. See <ulink url="http://tools.ietf.org/html/rfc7597">RFC 7597</ulink> for the details about the PSID wire representation</entry></row>
<row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
<row><entry>string</entry><entry>Any text</entry></row>
<row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
</section>
<section>
-<!-- @todo: add real meat to the prefix delegation config this is just place holder stuff -->
<title>Subnet and Prefix Delegation Pools</title>
<para>
Subnets may also be configured to delegate prefixes, as defined in
...
}</screen>
</para>
+
+ </section>
+
+ <section id="pd-exclude-option">
+ <title>Prefix Exclude Option</title>
+ <para>
+ For each delegated prefix the delegating router may choose to exclude
+ a single prefix out of the delegated prefix as specified in the
+ <ulink url="http://tools.ietf.org/html/rfc6603"> RFC 6603</ulink>.
+ The requesting router must not assign the excluded prefix to any
+ of its downstream interfaces and it is intended to be used on a
+ link through which the delegating router exchanges DHCPv6 messages with
+ the requesting router. The configuration example below demonstrates how
+ to specify an excluded prefix within a prefix pool definition. The
+ excluded prefix "2001:db8:1:babe:cafe:80::/72" will be sent to a
+ requesting router which includes Prefix Exclude option in the ORO, and
+ which is delegated a prefix from this pool.
+ </para>
+<screen>
+"Dhcp6": {
+ "subnet6": [
+ {
+ "subnet": "2001:db8:1::/48",
+ "pd-pools": [
+ {
+ "prefix": "2001:db8:1:8000::",
+ "prefix-len": 48,
+ "delegated-len": 64,
+ "excluded-prefix": "2001:db8:1:babe:cafe:80::",
+ "excluded-prefix-len": 72
+ }
+ ]
+ }
+ ]
+}
+</screen>
</section>
<section id="dhcp6-std-options">
<row><entry>bootfile-param</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
<row><entry>client-arch-type</entry><entry>61</entry><entry>uint16</entry><entry>true</entry></row>
<row><entry>nii</entry><entry>62</entry><entry>record (uint8, uint8, uint8)</entry><entry>false</entry></row>
+<row><entry>aftr-name</entry><entry>64</entry><entry>fqdn</entry><entry>false</entry></row>
<row><entry>erp-local-domain-name</entry><entry>65</entry><entry>fqdn</entry><entry>false</entry></row>
<row><entry>rsoo</entry><entry>66</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>pd-exclude</entry><entry>67</entry><entry>binary</entry><entry>false</entry></row>
<row><entry>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>
<!-- <row><entry>dhcpv4-message</entry><entry>87</entry><entry>binary</entry><entry>false</entry></row> -->
<row><entry>dhcp4o6-server-addr</entry><entry>88</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>s46-rule</entry><entry>89</entry><entry>record (uint8, uint8, uint8, ipv4-address, ipv6-prefix)</entry><entry>false</entry></row>
+<row><entry>s46-br</entry><entry>90</entry><entry>ipv6-address</entry><entry>false</entry></row>
+<row><entry>s46-dmr</entry><entry>91</entry><entry>ipv6-prefix</entry><entry>false</entry></row>
+<row><entry>s46-v4v6bind</entry><entry>92</entry><entry>record (ipv4-address, ipv6-prefix)</entry><entry>false</entry></row>
+<row><entry>s46-portparams</entry><entry>93</entry><entry>record(uint8, psid)</entry><entry>false</entry></row>
+<row><entry>s46-cont-mape</entry><entry>94</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>s46-cont-mapt</entry><entry>95</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>s46-cont-lw</entry><entry>96</entry><entry>empty</entry><entry>false</entry></row>
</tbody>
</tgroup>
</table>
</para>
</section>
+ <section id="s46-options">
+ <title>Common Softwire46 Options</title>
+ <para>
+ Softwire46 options are involved in IPv4 over IPv6 provisioning by
+ means of tunneling or translation as specified in the
+ <ulink url="http://tools.ietf.org/html/rfc7598">RFC 7598</ulink>.
+ The following sections provide configuration examples of these
+ options.
+ </para>
+
+ <section id="s46-containers">
+ <title>Softwire46 Container Options</title>
+ <para>
+ S46 container options group rules and optional port parameters
+ for a specified domain. There are three container options specified
+ in the "dhcp6" (top level) option space: MAP-E Container option,
+ MAP-T Container option and S46 Lieghtweight 4over6 Container option.
+ These options only contain encapsulated options specified below.
+ They do not include any data fields.
+ </para>
+
+ <para>
+ In order to configure the server to send specific container option
+ along with all encapsulated options, the container option must be
+ included in the server configuration as shown below:
+<screen>
+"Dhcp6": {
+ ...
+ "option-data": [
+ {
+ "name": "s46-cont-mape"
+ } ],
+ ...
+}
+</screen>
+
+ This configuration will cause the server to include MAP-E Container
+ option to the client. Use "s46-cont-mapt" or "s46-cont-lw" for the
+ MAP-T Container and S46 Lightweight 4over6 Container options
+ respectively.
+ </para>
+
+ <para>
+ All remaining softwire options described below are included in one
+ of the container options. Thus, they have to be included in appropriate
+ option spaces by selecting a "space" name, which specifies in which
+ option they are supposed to be included.
+ </para>
+ </section>
+
+ <section>
+ <title>S46 Rule Option</title>
+ <para>
+ The S46 Rule option is used for conveying the Basic Mapping Rule (BMR)
+ and Forwarding Mapping Rule (FMR).
+<screen>
+{
+ "space": "s46-cont-mape-options",
+ "name": "s46-rule",
+ "data": "1, 0, 24, 192.0.2.0, 2001:db8:1::/64"
+}
+</screen>
+ Other possible "space" value is "s46-cont-mapt-options".
+ </para>
+
+ </section>
+ <section>
+ <title>S46 BR Option</title>
+ <para>
+ The S46 BR option is used to convey the IPv6 address of the
+ Border Relay. This option is mandatory in the MAP-E
+ Container option and not permitted in the MAP-T and
+ S46 Lightweight 4over6 Container options.
+<screen>
+{
+ "space": "s46-cont-mape-options",
+ "name": "s46-br",
+ "data": "2001:db8:cafe::1",
+}
+</screen>
+ Other possible "space" value is "s46-cont-lw-options".
+ </para>
+ </section>
+
+ <section>
+ <title>S46 DMR Option</title>
+ <para>
+ The S46 DMR option is used to convey values for the Default
+ Mapping Rule (DMR). This option is mandatory in the MAP-T
+ container option and not permitted in the MAP-E and S46
+ Lightweight 4over6 Container options.
+<screen>
+{
+ "space": "s46-cont-mapt-options",
+ "name": "s46-dmr",
+ "data": "2001:db8:cafe::/64",
+}
+</screen>
+ This option must not be included in other containers.
+ </para>
+ </section>
+
+ <section>
+ <title>S46 IPv4/IPv6 Address Binding option.</title>
+ <para>
+ The S46 IPv4/IPv6 Address Binding option may be used to specify
+ the full or shared IPv4 address of the Customer Edge (CE).
+ The IPv6 prefix field is used by the CE to identify the
+ correct prefix to use for the tunnel source.
+<screen>
+{
+ "space": "s46-cont-lw",
+ "name": "s46-v4v6bind",
+ "data": "192.0.2.3, 2001:db8:1:cafe::/64"
+}
+</screen>
+ This option must not be included in other containers.
+ </para>
+ </section>
+ <section>
+ <title>S46 Port Parameters</title>
+ <para>
+ The S46 Port Parameters option specifies optional port set
+ information that MAY be provided to CEs
+<screen>
+{
+ "space": "s46-rule-options",
+ "name": "s46-portparams",
+ "data": "2, 3/4",
+}
+</screen>
+ Other possible "space" value is "s46-v4v6bind" to include
+ this option in the S46 IPv4/IPv6 Address Binding option.
+ </para>
+ <para>
+ Note that the second value in the example above specifies the
+ PSID and PSID length fields in the format of PSID/PSID length.
+ This is equivalent to the values of PSID-len=4 and
+ PSID=12288 conveyed in the S46 Port Parameters option.
+ </para>
+ </section>
+ </section>
+
<section id="dhcp6-custom-options">
<title>Custom DHCPv6 Options</title>
<para>It is possible to define options in addition to the standard ones.
"pd-pools": [
{
"prefix": "2001:db8:1:8000::",
- "prefix-len": 56,
- "delegated-len": 64
+ "prefix-len": 48,
+ "delegated-len": 64,
}
],
<userinput>"reservations": [
to echo back the options, checks whether an option is RSOO-enabled,
ability to mark additional options as RSOO-enabled.</simpara>
</listitem>
+ <listitem>
+ <simpara><emphasis>Prefix Exclude Option for DHCPv6-based Prefix
+ Delegation</emphasis>,
+ <ulink url="http://tools.ietf.org/html/rfc6603">RFC
+ 6603</ulink>: Prefix Exclude option is supported.</simpara>
+ </listitem>
<listitem>
<simpara><emphasis>Client Link-Layer Address Option in
DHCPv6</emphasis>,
7550</ulink>: All recommendations related to the DHCPv6 server
operation are supported.</simpara>
</listitem>
+ <listitem>
+ <simpara><emphasis>DHCPv6 Options for Configuration of Softwire
+ Address and Port-Mapped Clients</emphasis>,
+ <ulink url="http://tools.ietf.org/html/rfc7598">RFC
+ 7598</ulink>: All options specified in this specification are
+ supported by the DHCPv6 server.</simpara>
+ </listitem>
</itemizedlist>
</section>
// Iterate on the configured option list
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get("dhcp4", *opt);
+ OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
// Got it: add it and jump to the outer loop
if (desc.option_) {
resp->addOption(desc.option_);
// Check whether option has been configured.
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get("dhcp4", required_options[i]);
+ OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
+ required_options[i]);
if (desc.option_) {
resp->addOption(desc.option_);
break;
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
params["code"] = "56";
params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "dhcp-message";
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
params["code"] = param_value;
params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "dhcp-message";
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
params["code"] = "56";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
params["name"] = "dhcp-message";
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
params["code"] = "56";
params["data"] = "ABCDEF0105";
params["csv-format"] = param_value;
<< "does not exist in Config Manager";
}
OptionContainerPtr options =
- subnet->getCfgOption()->getAll("dhcp4");
+ subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
if (expected_options_count != options->size()) {
ADD_FAILURE() << "The number of options in the subnet '"
<< subnet_address.toText() << "' is different "
template<typename ReturnType>
ReturnType
retrieveOption(const Host& host, const uint16_t option_code) const {
- return (retrieveOption<ReturnType>(host, "dhcp4", option_code));
+ return (retrieveOption<ReturnType>(host, DHCP4_OPTION_SPACE, option_code));
}
/// @brief Retrieve an option associated with a host.
ElementPtr json = Element::fromJSON(config);
OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp4", 109);
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
ASSERT_FALSE(def);
// Use the configuration string to create new option definition.
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp4", 109);
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
ASSERT_TRUE(def);
// Check the option data.
checkResult(status, 0);
def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp4", 213);
+ getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 213);
ASSERT_TRUE(def);
// Check the option data.
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(0, options->size());
- options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+ options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
// These options are subnet options
OptionContainerPtr options =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(0, options->size());
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
- options = subnet->getCfgOption()->getAll("dhcp4");
+ options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
// Options should be now available
// Try to get the option from the space dhcp4.
OptionDescriptor desc1 =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 56);
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 56);
ASSERT_TRUE(desc1.option_);
EXPECT_EQ(56, desc1.option_->getType());
// Try to get the option from the space isc.
// We should have one option available.
OptionContainerPtr options =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(1, options->size());
// Get the option.
OptionDescriptor desc =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp4", 222);
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP4_OPTION_SPACE, 222);
EXPECT_TRUE(desc.option_);
EXPECT_EQ(222, desc.option_->getType());
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.24"));
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
// Create configuration. Use standard option 19 (ip-forwarding).
std::map<std::string, std::string> params;
params["name"] = "ip-forwarding";
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
params["code"] = "19";
params["data"] = "true";
params["csv-format"] = "true";
Subnet4Ptr subnet1 = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.100"));
ASSERT_TRUE(subnet1);
- OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp4");
+ OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
Subnet4Ptr subnet2 = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.3.102"));
ASSERT_TRUE(subnet2);
- OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp4");
+ OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(1, options2->size());
const OptionContainerTypeIndex& idx2 = options2->get<1>();
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp4");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
ConstElementPtr x;
std::map<std::string, std::string> params;
params["name"] = "nis-servers";
- params["space"] = "dhcp4";
+ params["space"] = DHCP4_OPTION_SPACE;
// Option code 41 means nis-servers.
params["code"] = "41";
// Specify option values in a CSV (user friendly) format.
getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.5"));
ASSERT_TRUE(subnet);
OptionContainerPtr options =
- subnet->getCfgOption()->getAll("dhcp4");
+ subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(1, options->size());
// We should have one option available.
OptionContainerPtr options =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp4");
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(1, options->size());
// Get the option.
OptionDescriptor desc = CfgMgr::instance().getStagingCfg()->
- getCfgOption()->get("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
EXPECT_TRUE(desc.option_);
EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option_->getType());
// client-id echo is optional
// rai echo is done in relayAgentInfoEcho
// Do subnet selection option
- OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
DHO_SUBNET_SELECTION);
ASSERT_TRUE(sbnsel_def);
OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
dis->addOption(clientid);
// Let's create a Relay Agent Information option
- OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(Option::V4,
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
DHO_DHCP_AGENT_OPTIONS);
ASSERT_TRUE(rai_def);
OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
EXPECT_TRUE(subnet2 == srv_.selectSubnet(dis));
// Subnet select option has a lower precedence
- OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
DHO_SUBNET_SELECTION);
ASSERT_TRUE(sbnsel_def);
OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
dis->addOption(clientid);
// Let's create a Subnet Selection option
- OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(Option::V4,
+ OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
DHO_SUBNET_SELECTION);
ASSERT_TRUE(sbnsel_def);
OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4));
// Add Router option.
Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
opt_routers->setAddress(IOAddress("192.0.2.2"));
- subnet_->getCfgOption()->add(opt_routers, false, "dhcp4");
+ subnet_->getCfgOption()->add(opt_routers, false, DHCP4_OPTION_SPACE);
CfgMgr::instance().clear();
CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
option_dns_servers->addAddress(IOAddress("192.0.2.1"));
option_dns_servers->addAddress(IOAddress("192.0.2.100"));
- ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, "dhcp4"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, DHCP4_OPTION_SPACE));
// domain-name
OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
option_domain_name->writeFqdn("example.com");
- subnet_->getCfgOption()->add(option_domain_name, false, "dhcp4");
+ subnet_->getCfgOption()->add(option_domain_name, false, DHCP4_OPTION_SPACE);
// log-servers
Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
option_log_servers->addAddress(IOAddress("192.0.2.2"));
option_log_servers->addAddress(IOAddress("192.0.2.10"));
- ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, "dhcp4"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, DHCP4_OPTION_SPACE));
// cookie-servers
Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
- ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, "dhcp4"));
+ ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, DHCP4_OPTION_SPACE));
}
void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
"item_optional": false,
"item_default": 128
},
+ {
+ "item_name": "excluded-prefix",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "excluded-prefix-len",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 128
+ },
{
"item_name": "option-data",
"item_type": "list",
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
#include <dhcp/option6_status_code.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
+#include <algorithm>
#include <stdlib.h>
#include <time.h>
#include <iomanip>
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ AllocEngine::ClientContext6& ctx,
const CfgOptionList& co_list) {
// Client requests some options using ORO option. Try to
boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
(question->getOption(D6O_ORO));
- // Option ORO not found? We're done here then.
- if (!option_oro || co_list.empty()) {
+ // 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 (co_list.empty()) {
+ // If there are no options configured, we at least have to check if
+ // the client has requested PD exclude, which is configured as
+ // part of the pool configuration.
+ ctx.pd_exclude_requested_ = (std::find(requested_opts.begin(),
+ requested_opts.end(),
+ D6O_PD_EXCLUDE) !=
+ requested_opts.end());
+ return;
+ }
+
BOOST_FOREACH(uint16_t opt, requested_opts) {
+ // Prefix Exclude option requires special handling, as it can
+ // be configured as part of the pool configuration.
+ if (opt == D6O_PD_EXCLUDE) {
+ ctx.pd_exclude_requested_ = true;
+ // Prefix Exclude can only be included in the IA Prefix option
+ // of IA_PD. Thus there is nothing more to do here.
+ continue;
+ }
// Iterate on the configured option list
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get("dhcp6", opt);
+ OptionDescriptor desc = (*copts)->get(DHCP6_OPTION_SPACE, opt);
// Got it: add it and jump to the outer loop
if (desc.option_) {
answer->addOption(desc.option_);
(*l)->prefixlen_, (*l)->preferred_lft_,
(*l)->valid_lft_));
ia_rsp->addOption(addr);
+
+ if (ctx.pd_exclude_requested_) {
+ // PD exclude option has been requested via ORO, thus we need to
+ // include it if the pool configuration specifies this option.
+ Pool6Ptr pool = boost::dynamic_pointer_cast<
+ Pool6>(subnet->getPool(Lease::TYPE_PD, (*l)->addr_));
+ if (pool && pool->getExcludedPrefixLength() > 0) {
+ OptionPtr opt(new Option6PDExclude((*l)->addr_,
+ (*l)->prefixlen_,
+ pool->getExcludedPrefix(),
+ pool->getExcludedPrefixLength()));
+ addr->addOption(opt);
+ }
+ }
}
// It would be possible to insert status code=0(success) as well,
// For all the leases we have now, add the IAPPREFIX with non-zero lifetimes
for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+
Option6IAPrefixPtr prf(new Option6IAPrefix(D6O_IAPREFIX,
(*l)->addr_, (*l)->prefixlen_,
(*l)->preferred_lft_, (*l)->valid_lft_));
ia_rsp->addOption(prf);
+
+ if (ctx.pd_exclude_requested_) {
+ // PD exclude option has been requested via ORO, thus we need to
+ // include it if the pool configuration specifies this option.
+ Pool6Ptr pool = boost::dynamic_pointer_cast<
+ Pool6>(subnet->getPool(Lease::TYPE_PD, (*l)->addr_));
+
+ if (pool && pool->getExcludedPrefixLength() > 0) {
+ OptionPtr opt(new Option6PDExclude((*l)->addr_,
+ (*l)->prefixlen_,
+ pool->getExcludedPrefix(),
+ pool->getExcludedPrefixLength()));
+ prf->addOption(opt);
+ }
+ }
+
+
LOG_INFO(lease6_logger, DHCP6_PD_LEASE_RENEW)
.arg(query->getLabel())
.arg((*l)->addr_.toText())
CfgOptionList co_list;
buildCfgOptionList(solicit, ctx, co_list);
appendDefaultOptions(solicit, response, co_list);
- appendRequestedOptions(solicit, response, co_list);
+ appendRequestedOptions(solicit, response, ctx, co_list);
appendRequestedVendorOptions(solicit, response, ctx, co_list);
// Only generate name change requests if sending a Reply as a result
CfgOptionList co_list;
buildCfgOptionList(request, ctx, co_list);
appendDefaultOptions(request, reply, co_list);
- appendRequestedOptions(request, reply, co_list);
+ appendRequestedOptions(request, reply, ctx, co_list);
appendRequestedVendorOptions(request, reply, ctx, co_list);
generateFqdn(reply);
CfgOptionList co_list;
buildCfgOptionList(renew, ctx, co_list);
appendDefaultOptions(renew, reply, co_list);
- appendRequestedOptions(renew, reply, co_list);
+ appendRequestedOptions(renew, reply, ctx, co_list);
appendRequestedVendorOptions(renew, reply, ctx, co_list);
generateFqdn(reply);
CfgOptionList co_list;
buildCfgOptionList(rebind, ctx, co_list);
appendDefaultOptions(rebind, reply, co_list);
- appendRequestedOptions(rebind, reply, co_list);
+ appendRequestedOptions(rebind, reply, ctx, co_list);
appendRequestedVendorOptions(rebind, reply, ctx, co_list);
generateFqdn(reply);
CfgOptionList co_list;
buildCfgOptionList(confirm, ctx, co_list);
appendDefaultOptions(confirm, reply, co_list);
- appendRequestedOptions(confirm, reply, co_list);
+ appendRequestedOptions(confirm, reply, ctx, co_list);
appendRequestedVendorOptions(confirm, reply, ctx, co_list);
// Indicates if at least one address has been verified. If no addresses
// are verified it means that the client has sent no IA_NA options
appendDefaultOptions(inf_request, reply, co_list);
// Try to assign options that were requested by the client.
- appendRequestedOptions(inf_request, reply, co_list);
+ appendRequestedOptions(inf_request, reply, ctx, co_list);
// Try to assigne vendor options that were requested by the client.
appendRequestedVendorOptions(inf_request, reply, ctx, co_list);
///
/// @param question client's message
/// @param answer server's message (options will be added here)
+ /// @param [out] ctx client context. This method sets the
+ /// ctx.pd_exclude_requested_ field to 'true' if the Prefix Exclude
+ /// option has been requested.
+ ///
/// @param co_list configured option list
void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ AllocEngine::ClientContext6& ctx,
const CfgOptionList& co_list);
/// @brief Appends requested vendor options to server's answer.
BOOST_FOREACH(ConfigPair param, pd_pool_->mapValue()) {
std::string entry(param.first);
ParserPtr parser;
- if (entry == "prefix") {
+ if (entry == "prefix" || entry =="excluded-prefix") {
StringParserPtr str_parser(new StringParser(entry,
string_values_));
parser = str_parser;
- } else if (entry == "prefix-len" || entry == "delegated-len") {
+ } else if (entry == "prefix-len" || entry == "delegated-len" ||
+ entry == "excluded-prefix-len") {
Uint32ParserPtr code_parser(new Uint32Parser(entry,
uint32_values_));
parser = code_parser;
// Try to obtain the pool parameters. It will throw an exception if any
// of the required parameters are not present or invalid.
try {
- std::string addr_str = string_values_->getParam("prefix");
- uint32_t prefix_len = uint32_values_->getParam("prefix-len");
- uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+ const std::string addr_str = string_values_->getParam("prefix");
+ const uint32_t prefix_len = uint32_values_->getParam("prefix-len");
+ const uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+ const std::string excluded_prefix_str =
+ string_values_->getOptionalParam("excluded-prefix", "::");
+ const uint32_t excluded_prefix_len =
+ uint32_values_->getOptionalParam("excluded-prefix-len", 0);
// Attempt to construct the local pool.
- pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
- prefix_len, delegated_len));
+ pool_.reset(new Pool6(IOAddress(addr_str), prefix_len,
+ delegated_len, IOAddress(excluded_prefix_str),
+ excluded_prefix_len));
// Merge options specified for a pool into pool configuration.
options_->copyTo(*pool_->getCfgOption());
} catch (const std::exception& ex) {
}
if (!code) {
- OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, option_str);
+ const OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE,
+ option_str);
if (def) {
code = def->getCode();
} else {
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
params["code"] = "38";
params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "subscriber-id";
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
params["code"] = param_value;
params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "subscriber-id";
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
params["code"] = "38";
params["data"] = param_value;
params["csv-format"] = "False";
} else if (parameter == "csv-format") {
params["name"] = "subscriber-id";
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
params["code"] = "38";
params["data"] = "ABCDEF0105";
params["csv-format"] = param_value;
<< " does not exist in Config Manager";
}
OptionContainerPtr options =
- subnet->getCfgOption()->getAll("dhcp6");
+ subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
if (expected_options_count != options->size()) {
ADD_FAILURE() << "The number of options in the subnet '"
<< subnet_address.toText() << "' is different "
template<typename ReturnType>
ReturnType
retrieveOption(const Host& host, const uint16_t option_code) const {
- return (retrieveOption<ReturnType>(host, "dhcp6", option_code));
+ return (retrieveOption<ReturnType>(host, DHCP6_OPTION_SPACE, option_code));
}
/// @brief Retrieve an option associated with a host.
EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
}
+// This test verifies that it is possible to specify a prefix pool with an
+// excluded prefix (see RFC6603).
+TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
+
+ ConstElementPtr x;
+
+ // Define a single valid pd pool.
+ string config =
+ "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64,"
+ " \"excluded-prefix\": \"3000:1::\","
+ " \"excluded-prefix-len\": 72"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }";
+
+ // Convert the JSON string into Elements.
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+
+ // Verify that DHCP6 configuration processing succeeds.
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 0 which indicates successful configuration processing.
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ checkResult(x, 0);
+
+ // Test that we can retrieve the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // Fetch the collection of PD pools. It should have 1 entry.
+ PoolCollection pc;
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+ EXPECT_EQ(1, pc.size());
+
+ // Get a pointer to the pd pool instance, and verify its contents.
+ Pool6Ptr p6;
+ ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+ ASSERT_TRUE(p6);
+ EXPECT_EQ("3000::", p6->getFirstAddress().toText());
+ EXPECT_EQ(64, p6->getLength());
+ EXPECT_EQ("3000:1::", p6->getExcludedPrefix().toText());
+ EXPECT_EQ(72, static_cast<unsigned>(p6->getExcludedPrefixLength()));
+}
+
// Goal of this test is verify that a list of PD pools can be configured.
// It also verifies that a subnet may be configured with both regular pools
// and pd pools.
ElementPtr json = Element::fromJSON(config);
OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp6", 100);
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
ASSERT_FALSE(def);
// Use the configuration string to create new option definition.
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp6", 100);
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
ASSERT_TRUE(def);
// Check the option data.
checkResult(status, 0);
def = CfgMgr::instance().getStagingCfg()->
- getCfgOptionDef()->get("dhcp6", 63);
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 63);
ASSERT_TRUE(def);
// Check the option data.
Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(0, options->size());
- options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+ options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
// These options are subnet options
OptionContainerPtr options =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(0, options->size());
Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet);
- options = subnet->getCfgOption()->getAll("dhcp6");
+ options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
// Options should be now available
// Try to get the option from the space dhcp6.
OptionDescriptor desc1 =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 38);
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 38);
ASSERT_TRUE(desc1.option_);
EXPECT_EQ(38, desc1.option_->getType());
// Try to get the option from the space isc.
// We should have one option available.
OptionContainerPtr options =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll("dhcp6");
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(1, options->size());
// Get the option.
OptionDescriptor desc =
- CfgMgr::instance().getStagingCfg()->getCfgOption()->get("dhcp6", 100);
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 100);
EXPECT_TRUE(desc.option_);
EXPECT_EQ(100, desc.option_->getType());
Subnet6Ptr subnet1 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet1);
- OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
Subnet6Ptr subnet2 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:2::4"), classify_);
ASSERT_TRUE(subnet2);
- OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(1, options2->size());
const OptionContainerTypeIndex& idx2 = options2->get<1>();
// Create configuration. Use standard option 1000.
std::map<std::string, std::string> params;
params["name"] = "bool-option";
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
params["code"] = "1000";
params["data"] = "true";
params["csv-format"] = "true";
Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
ConstElementPtr x;
std::map<std::string, std::string> params;
params["name"] = "ia-na";
- params["space"] = "dhcp6";
+ params["space"] = DHCP6_OPTION_SPACE;
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
params["data"] = "12345, 6789, 1516";
Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet);
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
ASSERT_TRUE(subnet);
// We should have one option available.
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(1, options->size());
// Get the option.
- OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", D6O_VENDOR_OPTS);
+ OptionDescriptor desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_VENDOR_OPTS);
EXPECT_TRUE(desc.option_);
EXPECT_EQ(D6O_VENDOR_OPTS, desc.option_->getType());
/// @param payload specified payload (0 = fill payload with repeating option code)
/// @return RSOO with nested options
OptionPtr createRSOO(const std::vector<uint16_t>& codes, uint8_t payload = 0) {
- OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, D6O_RSOO);
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_RSOO);
if (!def) {
isc_throw(BadValue, "Can't find RSOO definition");
}
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/tests/dhcp6_message_test.h>
/// - an option with unique value specified for each pool, so as it is
/// possible to test that pool specific options can be assigned.
///
+/// - Configuration 5:
+/// - addresses and prefixes
+/// - 1 subnet with one address pool and one prefix pool
+/// - address pool: 2001:db8:1::/64
+/// - prefix pool: 3000::/72
+/// - excluded prefix 3000::1000/120 in a prefix pool.
+///
const char* RENEW_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000"
- "}"
+ "}",
+
+// Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80,"
+ " \"excluded-prefix\": \"3000::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }
};
/// @brief Test fixture class for testing Renew.
EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
}
+// Test that it is possible to renew a prefix lease with a Prefix Exclude
+// option being included during renew.
+TEST_F(RenewTest, renewWithExcludedPrefix) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Request Prefix Exclude option.
+ client.requestOption(D6O_PD_EXCLUDE);
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The client should also acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Make sure that Prefix Exclude option hasn't been included.
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_FALSE(option);
+
+ // Reconfigure the server to use the prefix pool with excluded prefix.
+ configure(RENEW_CONFIGS[4], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Make sure that the client has acquired PD lease.
+ leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // The leases should have been renewed.
+ EXPECT_EQ(1000, leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+ EXPECT_EQ(1000, leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_);
+
+ // This time, the Prefix Exclude option should be included.
+ option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
// This test verifies that the client can request a prefix delegation
// with a hint, while it is renewing an address lease.
TEST_F(RenewTest, requestPrefixInRenewUseHint) {
#include <config.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <dhcpsrv/cfgmgr.h>
/// - an option with unique value specified for each pool, so as it is
/// possible to test that pool specific options can be assigned.
///
+/// Configuration 3:
+/// - one subnet 3000::/32 used on eth0 interface
+/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
+/// - Excluded Prefix specified (RFC 6603).
+///
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
" \"interface\": \"eth0\""
" } ],"
"\"valid-lifetime\": 4000"
- "}"
+ "}",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64,"
+ " \"excluded-prefix\": \"2001:db8:3::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
};
/// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
}
+/// This test verifies that it is possible to specify an excluded prefix
+/// (RFC 6603) and send it back to the client requesting prefix delegation.
+TEST_F(SARRTest, directClientExcludedPrefix) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ client.requestOption(D6O_PD_EXCLUDE);
+ configure(CONFIGS[2], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ Lease6Ptr lease_server = checkLease(lease_client);
+ // Check that the server recorded the lease.
+ ASSERT_TRUE(lease_server);
+
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option);
+ ASSERT_TRUE(ia);
+ option = ia->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast<Option6IAPrefix>(option);
+ ASSERT_TRUE(pd_option);
+ option = pd_option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
// Check that when the client includes the Rapid Commit option in its
// Solicit, the server responds with Reply and commits the lease.
TEST_F(SARRTest, rapidCommitEnable) {
libkea_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libkea_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libkea_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
+libkea_dhcp___la_SOURCES += option6_pdexclude.cc option6_pdexclude.h
libkea_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
libkea_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
libkea_dhcp___la_SOURCES += option6_status_code.cc option6_status_code.h
#define D6O_CLIENT_ARCH_TYPE 61 /* RFC5970 */
#define D6O_NII 62 /* RFC5970 */
//#define D6O_GEOLOCATION 63 /* RFC6225 */
-//#define D6O_AFTR_NAME 64 /* RFC6334 */
+#define D6O_AFTR_NAME 64 /* RFC6334 */
#define D6O_ERP_LOCAL_DOMAIN_NAME 65 /* RFC6440 */
#define D6O_RSOO 66 /* RFC6422 */
-//#define D6O_PD_EXCLUDE 67 /* RFC6603 */
+#define D6O_PD_EXCLUDE 67 /* RFC6603 */
//#define D6O_VSS 68 /* RFC6607 */
//#define D6O_MIP6_IDINF 69 /* RFC6610 */
//#define D6O_MIP6_UDINF 70 /* RFC6610 */
//#define D6O_V6_PCP_SERVER 86 /* RFC7291 */
#define D6O_DHCPV4_MSG 87 /* RFC7341 */
#define D6O_DHCPV4_O_DHCPV6_SERVER 88 /* RFC7341 */
-//#define D6O_S46_RULE 89 /* RFC7598 */
-//#define D6O_S46_BR 90 /* RFC7598 */
-//#define D6O_S46_DMR 91 /* RFC7598 */
-//#define D6O_S46_V4V6BIND 92 /* RFC7598 */
-//#define D6O_S46_PORTPARAMS 93 /* RFC7598 */
-//#define D6O_S46_CONT_MAPE 94 /* RFC7598 */
-//#define D6O_S46_CONT_MAPT 95 /* RFC7598 */
-//#define D6O_S46_CONT_LW 96 /* RFC7598 */
+#define D6O_S46_RULE 89 /* RFC7598 */
+#define D6O_S46_BR 90 /* RFC7598 */
+#define D6O_S46_DMR 91 /* RFC7598 */
+#define D6O_S46_V4V6BIND 92 /* RFC7598 */
+#define D6O_S46_PORTPARAMS 93 /* RFC7598 */
+#define D6O_S46_CONT_MAPE 94 /* RFC7598 */
+#define D6O_S46_CONT_MAPT 95 /* RFC7598 */
+#define D6O_S46_CONT_LW 96 /* RFC7598 */
//#define D6O_4RD 97 /* RFC7600 */
//#define D6O_4RD_MAP_RULE 98 /* RFC7600 */
//#define D6O_4RD_NON_MAP_RULE 99 /* RFC7600 */
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 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
};
/// Number of option definitions defined.
-const int DOCSIS3_V6_DEFS_SIZE = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
+const int DOCSIS3_V6_DEFS_SIZE =
+ sizeof(DOCSIS3_V6_DEFS) / sizeof(DOCSIS3_V6_DEFS[0]);
/// The class as specified in vendor-class option by the devices
extern const char* DOCSIS3_CLASS_EROUTER;
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 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
/// @brief Minimal length of the MAC address.
const size_t MIN_MAC_LEN = 6;
-/// @brief Length of the enterprise if field.
+/// @brief Length of the enterprise ID field.
const size_t ENTERPRISE_ID_LEN = 4;
/// @brief Default length of the variable length identifier in the DUID-EN.
// Static container with DHCPv6 option definitions.
OptionDefContainerPtr LibDHCP::v6option_defs_(new OptionDefContainer());
+// Static container with option definitions grouped by option space.
+OptionDefContainers LibDHCP::option_defs_;
+
+// Static container with vendor option definitions for DHCPv4.
VendorOptionDefContainers LibDHCP::vendor4_defs_;
+// Static container with vendor option definitions for DHCPv6.
VendorOptionDefContainers LibDHCP::vendor6_defs_;
// Static container with option definitions created in runtime.
size_t params_size);
const OptionDefContainerPtr&
-LibDHCP::getOptionDefs(const Option::Universe u) {
- switch (u) {
- case Option::V4:
- if (v4option_defs_->empty()) {
- initStdOptionDefs4();
- initVendorOptsDocsis4();
- }
+LibDHCP::getOptionDefs(const std::string& space) {
+ // If any of the containers is not initialized, it means that we haven't
+ // initialized option definitions at all.
+ if (v4option_defs_->empty()) {
+ initStdOptionDefs4();
+ initVendorOptsDocsis4();
+ initStdOptionDefs6();
+ initVendorOptsDocsis6();
+ }
+
+ if (space == DHCP4_OPTION_SPACE) {
return (v4option_defs_);
- case Option::V6:
- if (v6option_defs_->empty()) {
- initStdOptionDefs6();
- initVendorOptsDocsis6();
- }
+
+ } else if (space == DHCP6_OPTION_SPACE) {
return (v6option_defs_);
- default:
- isc_throw(isc::BadValue, "invalid universe " << u << " specified");
}
+
+ OptionDefContainers::const_iterator container = option_defs_.find(space);
+ if (container != option_defs_.end()) {
+ return (container->second);
+ }
+ return (null_option_def_container_);
}
const OptionDefContainerPtr&
}
OptionDefinitionPtr
-LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
- const OptionDefContainerPtr& defs = getOptionDefs(u);
+LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
+ const OptionDefContainerPtr& defs = getOptionDefs(space);
const OptionDefContainerTypeIndex& idx = defs->get<1>();
const OptionDefContainerTypeRange& range = idx.equal_range(code);
if (range.first != range.second) {
}
OptionDefinitionPtr
-LibDHCP::getOptionDef(const Option::Universe u, const std::string& name) {
- const OptionDefContainerPtr defs = getOptionDefs(u);
+LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
+ const OptionDefContainerPtr defs = getOptionDefs(space);
const OptionDefContainerNameIndex& idx = defs->get<2>();
const OptionDefContainerNameRange& range = idx.equal_range(name);
if (range.first != range.second) {
return (*range.first);
}
return (OptionDefinitionPtr());
-
}
-
OptionDefinitionPtr
LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
const std::string& name) {
runtime_option_defs_.commit();
}
-bool
-LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
- if (u == Option::V6) {
- if (code < 79 &&
- code != 10 &&
- code != 35) {
- return (true);
- }
-
- } else if (u == Option::V4) {
- if (!(code == 84 ||
- code == 96 ||
- (code > 101 && code < 112) ||
- code == 115 ||
- code == 126 ||
- code == 127 ||
- (code > 146 && code < 150) ||
- (code > 177 && code < 208) ||
- (code > 213 && code < 220) ||
- (code > 221 && code < 255))) {
- return (true);
- }
-
- }
-
- return (false);
-}
-
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
size_t last_offset = 0;
// Get the list of standard option definitions.
- const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(Option::V6);
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
// Runtime option definitions for non standard option space and if
// the definition doesn't exist within the standard option definitions.
const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
size_t last_offset = 0;
// Get the list of standard option definitions.
- const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(Option::V4);
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
// Runtime option definitions for non standard option space and if
// the definition doesn't exist within the standard option definitions.
const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
void
LibDHCP::initStdOptionDefs4() {
- initOptionSpace(v4option_defs_, OPTION_DEF_PARAMS4, OPTION_DEF_PARAMS_SIZE4);
+ initOptionSpace(v4option_defs_, STANDARD_V4_OPTION_DEFINITIONS,
+ STANDARD_V4_OPTION_DEFINITIONS_SIZE);
}
void
LibDHCP::initStdOptionDefs6() {
- initOptionSpace(v6option_defs_, OPTION_DEF_PARAMS6, OPTION_DEF_PARAMS_SIZE6);
+ initOptionSpace(v6option_defs_, STANDARD_V6_OPTION_DEFINITIONS,
+ STANDARD_V6_OPTION_DEFINITIONS_SIZE);
+ initOptionSpace(option_defs_[MAPE_V6_OPTION_SPACE], MAPE_V6_OPTION_DEFINITIONS,
+ MAPE_V6_OPTION_DEFINITIONS_SIZE);
+ initOptionSpace(option_defs_[MAPT_V6_OPTION_SPACE], MAPT_V6_OPTION_DEFINITIONS,
+ MAPT_V6_OPTION_DEFINITIONS_SIZE);
+ initOptionSpace(option_defs_[LW_V6_OPTION_SPACE], LW_V6_OPTION_DEFINITIONS,
+ LW_V6_OPTION_DEFINITIONS_SIZE);
+ initOptionSpace(option_defs_[V4V6_RULE_OPTION_SPACE], V4V6_RULE_OPTION_DEFINITIONS,
+ V4V6_RULE_OPTION_DEFINITIONS_SIZE);
+ initOptionSpace(option_defs_[V4V6_BIND_OPTION_SPACE], V4V6_BIND_OPTION_DEFINITIONS,
+ V4V6_BIND_OPTION_DEFINITIONS_SIZE);
}
void
LibDHCP::initVendorOptsDocsis4() {
- initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE);
+ initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS,
+ DOCSIS3_V4_DEFS_SIZE);
}
void
LibDHCP::initVendorOptsDocsis6() {
- initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE);
+ initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS,
+ DOCSIS3_V6_DEFS_SIZE);
}
void
LibDHCP::initVendorOptsIsc6() {
- initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE);
+ initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_OPTION_DEFINITIONS,
+ ISC_V6_OPTION_DEFINITIONS_SIZE);
}
uint32_t
/// Map of factory functions.
typedef std::map<unsigned short, Option::Factory*> FactoryMap;
- /// @brief Return collection of option definitions.
+ /// @brief Returns collection of option definitions.
///
- /// Method returns the collection of DHCP standard DHCP
- /// option definitions.
- /// @todo DHCPv4 option definitions are not implemented. For now
- /// this function will throw isc::NotImplemented in case of attempt
- /// to get option definitions for V4 universe.
+ /// This method returns a collection of option definitions for a specified
+ /// option space.
///
- /// @param u universe of the options (V4 or V6).
+ /// @param space Option space.
///
/// @return Pointer to a collection of option definitions.
- static const OptionDefContainerPtr& getOptionDefs(const Option::Universe u);
+ static const OptionDefContainerPtr& getOptionDefs(const std::string& space);
/// @brief Return the first option definition matching a
/// particular option code.
///
- /// @param u universe (V4 or V6)
+ /// @param space option space.
/// @param code option code.
///
/// @return reference to an option definition being requested
/// or NULL pointer if option definition has not been found.
- static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+ static OptionDefinitionPtr getOptionDef(const std::string& space,
const uint16_t code);
/// @brief Return the definition of option having a specified name.
///
- /// @param u universe (v4 or V6)
+ /// @param space option space.
/// @param name Option name.
///
/// @return Pointer to the option definition or NULL pointer if option
/// definition has not been found.
- static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+ static OptionDefinitionPtr getOptionDef(const std::string& option_space,
const std::string& name);
/// @brief Returns vendor option definition for a given vendor-id and code
static OptionDefContainerPtr
getRuntimeOptionDefs(const std::string& space);
- /// @brief Check if the specified option is a standard option.
- ///
- /// @param u universe (V4 or V6)
- /// @param code option code.
- ///
- /// @return true if the specified option is a standard option.
- /// @todo We already create option definitions for the subset if
- /// standard options. We are aiming that this function checks
- /// the presence of the standard option definition and if it finds
- /// it, then the true value is returned. However, at this point
- /// this is not doable because some of the definitions (for less
- /// important options) are not created yet.
- static bool isStandardOption(const Option::Universe u,
- const uint16_t code);
-
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
/// Container with DHCPv6 option definitions.
static OptionDefContainerPtr v6option_defs_;
+ /// Container that holds option definitions for various option spaces.
+ static OptionDefContainers option_defs_;
+
/// Container for v4 vendor option definitions
static VendorOptionDefContainers vendor4_defs_;
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/option_space.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
Option6IA::Option6IA(uint16_t type, uint32_t iaid)
:Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) {
- // IA_TA has different layout than IA_NA and IA_PD. We can't sue this class
+ // IA_TA has different layout than IA_NA and IA_PD. We can't use this class
if (type == D6O_IA_TA) {
isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
"a different layout");
}
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
}
Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
"a different layout");
}
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
unpack(begin, end);
}
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_space.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
uint32_t pref, uint32_t valid)
:Option(V6, type), addr_(addr), preferred_(pref),
valid_(valid) {
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
if (!addr.isV6()) {
isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
}
Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end)
:Option(V6, type), addr_("::") {
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
unpack(begin, end);
}
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option_space.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len, uint32_t pref, uint32_t valid)
:Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
// Option6IAAddr will check if prefix is IPv6 and will throw if it is not
if (prefix_len > 128) {
isc_throw(BadValue, static_cast<unsigned>(prefix_len)
Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end)
:Option6IAAddr(type, begin, end) {
- setEncapsulatedSpace("dhcp6");
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
unpack(begin, end);
}
--- /dev/null
+// Copyright (C) 2016 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option6_pdexclude.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <boost/dynamic_bitset.hpp>
+#include <iostream>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ const isc::asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_length)
+ : Option(V6, D6O_PD_EXCLUDE),
+ delegated_prefix_(delegated_prefix),
+ delegated_prefix_length_(delegated_prefix_length),
+ excluded_prefix_(excluded_prefix),
+ excluded_prefix_length_(excluded_prefix_length) {
+
+ // Expecting v6 prefixes of sane length.
+ if (!delegated_prefix_.isV6() || !excluded_prefix_.isV6() ||
+ (delegated_prefix_length_ > 128) || (excluded_prefix_length_ > 128)) {
+ isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
+ << delegated_prefix_ << "/"
+ << static_cast<int>(delegated_prefix_length_) << ", "
+ << excluded_prefix_ << "/"
+ << static_cast<int>(excluded_prefix_length_));
+ }
+
+ // Excluded prefix must be longer than the delegated prefix.
+ if (excluded_prefix_length_ <= delegated_prefix_length_) {
+ isc_throw(BadValue, "length of the excluded prefix "
+ << excluded_prefix_ << "/"
+ << static_cast<int>(excluded_prefix_length_)
+ << " must be greater than the length of the"
+ " delegated prefix " << delegated_prefix_ << "/"
+ << static_cast<int>(delegated_prefix_length_));
+ }
+
+ // Both prefixes must share common part with a length equal to the
+ // delegated prefix length.
+ std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+ boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
+ delegated_prefix_bytes.rend());
+
+ std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+ boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
+ excluded_prefix_bytes.rend());
+
+
+ // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
+ const uint8_t delta = 128 - delegated_prefix_length;
+
+ if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
+ isc_throw(BadValue, "excluded prefix "
+ << excluded_prefix_ << "/"
+ << static_cast<int>(excluded_prefix_length_)
+ << " must have the same common prefix part of "
+ << static_cast<int>(delegated_prefix_length)
+ << " as the delegated prefix "
+ << delegated_prefix_ << "/"
+ << static_cast<int>(delegated_prefix_length_));
+ }
+
+}
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(V6, D6O_PD_EXCLUDE),
+ delegated_prefix_(delegated_prefix),
+ delegated_prefix_length_(delegated_prefix_length),
+ excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+ excluded_prefix_length_(0) {
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6PDExclude::clone() const {
+ return (cloneInternal<Option6PDExclude>());
+}
+
+void
+Option6PDExclude::pack(isc::util::OutputBuffer& buf) const {
+ // Header = option code and length.
+ packHeader(buf);
+
+ // Excluded prefix length is always 1 byte long field.
+ buf.writeUint8(excluded_prefix_length_);
+
+ // Retrieve entire prefix and convert it to bit representation.
+ std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+ boost::dynamic_bitset<uint8_t> bits(excluded_prefix_bytes.rbegin(),
+ excluded_prefix_bytes.rend());
+
+ // Shifting prefix by delegated prefix length leaves us with only a
+ // subnet id part of the excluded prefix.
+ bits = bits << delegated_prefix_length_;
+
+ // Calculate subnet id length.
+ const uint8_t subnet_id_length = getSubnetIDLength();
+ for (uint8_t i = 0; i < subnet_id_length; ++i) {
+ // Retrieve bit representation of the current byte.
+ const boost::dynamic_bitset<uint8_t> first_byte = bits >> 120;
+ // Convert it to a numeric value.
+ uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
+
+ // Zero padded bits follow when excluded_prefix_length_ is not divisible by 8.
+ if (i == subnet_id_length - 1) {
+ uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length_;
+ uint8_t mask = 0xFF;
+ mask <<= (8 - (length_delta % 8));
+ val &= mask;
+ }
+ // Store calculated value in a buffer.
+ buf.writeUint8(val);
+
+ // Go to the next byte.
+ bits <<= 8;
+ }
+}
+
+void
+Option6PDExclude::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+
+ // At this point we don't know the excluded prefix length, but the
+ // minimum requirement is that reminder of this option includes the
+ // excluded prefix length and at least 1 byte of the IPv6 subnet id.
+ if (std::distance(begin, end) < 2) {
+ isc_throw(BadValue, "truncated Prefix Exclude option");
+ }
+
+ // We can safely read the excluded prefix length and move forward.
+ excluded_prefix_length_ = *begin++;
+
+ // We parsed the excluded prefix length so we can now determine the
+ // size of the IPv6 subnet id. The reminder of the option should
+ // include data of that size. If the option size is lower than the
+ // subnet id length we report an error.
+ const unsigned int subnet_id_length = getSubnetIDLength();
+ if (subnet_id_length > std::distance(begin, end)) {
+ isc_throw(BadValue, "truncated Prefix Exclude option, expected "
+ "IPv6 subnet id length is " << subnet_id_length);
+ }
+
+ // Get binary representation of the delegated prefix.
+ std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+ // We need to calculate how many bytes include the useful data and assign
+ // zeros to remaining bytes (beyond the prefix length).
+ const uint8_t bytes_length = (delegated_prefix_length_ / 8) +
+ static_cast<uint8_t>(delegated_prefix_length_ % 8 != 0);
+ std::fill(delegated_prefix_bytes.begin() + bytes_length,
+ delegated_prefix_bytes.end(), 0);
+
+ // Convert the delegated prefix to bit format.
+ boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
+ delegated_prefix_bytes.rend());
+
+ // Convert subnet id to bit format.
+ std::vector<uint8_t> subnet_id_bytes(begin, end);
+ boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_bytes.rbegin(),
+ subnet_id_bytes.rend());
+
+ // Subnet id parsed, proceed to the end of the option.
+ begin = end;
+
+ // Concatenate the delegated prefix with subnet id. The resulting prefix
+ // is an excluded prefix in bit format.
+ for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
+ bits.set(128 - delegated_prefix_length_ - subnet_id_bits.size() + i,
+ subnet_id_bits.test(i));
+ }
+
+ // Convert the prefix to binary format.
+ std::vector<uint8_t> bytes(V6ADDRESS_LEN);
+ boost::to_block_range(bits, bytes.rbegin());
+
+ // And create a prefix object from bytes.
+ excluded_prefix_ = IOAddress::fromBytes(AF_INET6, &bytes[0]);
+}
+
+uint16_t
+Option6PDExclude::len() const {
+ return (getHeaderLen() + sizeof(excluded_prefix_length_)
+ + getSubnetIDLength());
+}
+
+std::string
+Option6PDExclude::toText(int indent) const {
+ std::ostringstream s;
+ s << headerToText(indent) << ": ";
+ s << excluded_prefix_ << "/"
+ << static_cast<int>(excluded_prefix_length_);
+ return (s.str());
+}
+
+uint8_t
+Option6PDExclude::getSubnetIDLength() const {
+ uint8_t subnet_id_length_bits = excluded_prefix_length_ -
+ delegated_prefix_length_ - 1;
+ uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
+ return (subnet_id_length);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2016 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef OPTION6_PDEXCLUDE_H
+#define OPTION6_PDEXCLUDE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv6 option class representing Prefix Exclude Option (RFC 6603).
+///
+/// This class represents DHCPv6 Prefix Exclude option (67). This option is
+/// carried in the IA Prefix option and it conveys a single prefix which is
+/// used by the delegating router to communicate with a requesting router on
+/// the requesting router's uplink. This prefix is not used on the
+/// requesting router's downlinks (is excluded from other delegated prefixes).
+class Option6PDExclude: public Option {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param delegated_prefix Delagated prefix.
+ /// @param delegated_prefix_length Delegated prefix length.
+ /// @param excluded_prefix Excluded prefix.
+ /// @param excluded_prefix_length Excluded prefix length.
+ ///
+ /// @throw BadValue if prefixes are invalid, if excluded prefix length
+ /// is not greater than delegated prefix length or if common parts of
+ /// prefixes does not match.
+ Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ const isc::asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_length);
+
+ /// @brief Constructor, creates option instance from part of the buffer.
+ ///
+ /// This constructor is mostly used to parse Prefix Exclude options in the
+ /// received messages.
+ ///
+ /// @param begin Lower bound of the buffer to create option from.
+ /// @param end Upper bound of the buffer to create option from.
+ Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format to a buffer.
+ ///
+ /// Writes option in wire-format to buffer, returns pointer to first unused
+ /// byte after stored option (that is useful for writing options one after
+ /// another).
+ ///
+ /// The format of the option includes excluded prefix length specified as
+ /// a number of bits. It also includes IPv6 subnet ID field which is
+ /// computed from the delegated and excluded prefixes, according to the
+ /// section 4.2 of RFC 6603.
+ ///
+ /// @param [out] buf Pointer to a buffer.
+ virtual void pack(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns length of the complete option (data length + DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len() const;
+
+ /// @brief Returns Prefix Exclude option in textual format.
+ ///
+ /// @param ident Number of spaces to be inserted before the text.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns delegated prefix.
+ isc::asiolink::IOAddress getDelegatedPrefix() const {
+ return (delegated_prefix_);
+ }
+
+ /// @brief Returns delegated prefix length.
+ uint8_t getDelegatedPrefixLength() const {
+ return (delegated_prefix_length_);
+ }
+
+ /// @brief Returns excluded prefix.
+ isc::asiolink::IOAddress getExcludedPrefix() const {
+ return (excluded_prefix_);
+ }
+
+ /// @brief Returns excluded prefix length.
+ uint8_t getExcludedPrefixLength() const {
+ return (excluded_prefix_length_);
+ }
+
+private:
+
+ /// @brief Returns IPv6 subnet ID length in octets.
+ ///
+ /// The IPv6 subnet ID length is between 1 and 16 octets.
+ uint8_t getSubnetIDLength() const;
+
+ /// @brief Holds delegated prefix.
+ isc::asiolink::IOAddress delegated_prefix_;
+
+ /// @brief Holds delegated prefix length,
+ uint8_t delegated_prefix_length_;
+
+ /// @brief Holds excluded prefix.
+ isc::asiolink::IOAddress excluded_prefix_;
+
+ /// @brief Holds excluded prefix length.
+ uint8_t excluded_prefix_length_;
+};
+
+/// @brief Pointer to the @ref Option6PDExclude object.
+typedef boost::shared_ptr<Option6PDExclude> Option6PDExcludePtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION6_PDEXCLUDE_H
#include <dhcp/option_custom.h>
#include <util/encode/hex.h>
+using namespace isc::asiolink;
+
namespace isc {
namespace dhcp {
}
void
-OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+OptionCustom::addArrayDataField(const IOAddress& address) {
checkArrayType();
if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
buffers_.push_back(buf);
}
+void
+OptionCustom::addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) {
+ isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for"
+ " an option comprising an array of IPv6 prefix values");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_PSID_TYPE) {
+ isc_throw(BadDataTypeCast, "PSID value can be specified onlu for"
+ " an option comprising an array of PSID length / value"
+ " tuples");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ buffers_.push_back(buf);
+}
+
void
OptionCustom::checkIndex(const uint32_t index) const {
if (index >= buffers_.size()) {
// For variable data sizes the utility function returns zero.
// It is ok for string values because the default string
// is 'empty'. However for FQDN the empty value is not valid
- // so we initialize it to '.'.
- if (data_size == 0 &&
- *field == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
+ // so we initialize it to '.'. For prefix there is a prefix
+ // length fixed field.
+ if (data_size == 0) {
+ if (*field == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+
+ } else if (*field == OPT_IPV6_PREFIX_TYPE) {
+ OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress::IPV6_ZERO_ADDRESS(),
+ buf);
+ }
} else {
// At this point we can resize the buffer. Note that
// for string values we are setting the empty buffer
// so we have to allocate exactly one buffer.
OptionBuffer buf;
size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
- if (data_size == 0 &&
- data_type == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
+ if (data_size == 0) {
+ if (data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress::IPV6_ZERO_ADDRESS(),
+ buf);
+ }
} else {
// Note that if our option holds a string value then
// we are making empty buffer here.
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
- } else if ( (*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE) ) {
+ } else if ((*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE)) {
// In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
// that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end());
+ } else if (*field == OPT_IPV6_PREFIX_TYPE ) {
+ // The size of the IPV6 prefix type is determined as
+ // one byte (which is the size of the prefix in bits)
+ // followed by the prefix bits (right-padded with
+ // zeros to the nearest octet boundary).
+ if (std::distance(data, data_buf.end()) > 0) {
+ data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
+ }
} else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
// an option field.
isc_throw(OutOfRange, "option buffer truncated");
}
- } else {
- // Our data field requires that there is a certain chunk of
- // data left in the buffer. If not, option is truncated.
- if (std::distance(data, data_buf.end()) < data_size) {
- isc_throw(OutOfRange, "option buffer truncated");
- }
}
+
+ // Our data field requires that there is a certain chunk of
+ // data left in the buffer. If not, option is truncated.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+
// Store the created buffer.
buffers.push_back(OptionBuffer(data, data + data_size));
// Proceed to the next data field.
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
+
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ PrefixTuple prefix =
+ OptionDataTypeUtil::readPrefix(OptionBuffer(data, data_buf.end()));
+ // Data size comprises 1 byte holding a prefix length and the
+ // prefix length (in bytes) rounded to the nearest byte boundary.
+ data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
}
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
// 1 bytes larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
+
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ if (!data_buf.empty()) {
+ data_size = static_cast<size_t>
+ (sizeof(uint8_t) + (data_buf[0] + 7) / 8);
+ }
} else {
data_size = std::distance(data, data_buf.end());
}
}
- if (data_size > 0) {
+ if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
} else {
}
-asiolink::IOAddress
+IOAddress
OptionCustom::readAddress(const uint32_t index) const {
checkIndex(index);
}
void
-OptionCustom::writeAddress(const asiolink::IOAddress& address,
+OptionCustom::writeAddress(const IOAddress& address,
const uint32_t index) {
- using namespace isc::asiolink;
-
checkIndex(index);
if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
std::swap(buffers_[index], buf);
}
+PrefixTuple
+OptionCustom::readPrefix(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPrefix(buffers_[index]));
+}
+
+void
+OptionCustom::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
+PSIDTuple
+OptionCustom::readPsid(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPsid(buffers_[index]));
+}
+
+void
+OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
#ifndef OPTION_CUSTOM_H
#define OPTION_CUSTOM_H
+#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <util/io_utilities.h>
buffers_.push_back(buf);
}
+ /// @brief Create new buffer and store variable length prefix in it.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix.
+ void addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix);
+
+ /// @brief Create new buffer and store PSID length / value in it.
+ ///
+ /// @param psid_len PSID length.
+ /// @param psid PSID.
+ void addArrayDataField(const PSIDLen& psid_len, const PSID& psid);
+
/// @brief Return a number of the data fields.
///
/// @return number of data fields held by the option.
std::swap(buffers_[index], buf);
}
+ /// @brief Read a buffer as variable length prefix.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return Prefix length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PrefixTuple readPrefix(const uint32_t index = 0) const;
+
+ /// @brief Write prefix length and value into a buffer.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix value.
+ /// @param index Buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as a PSID length / value tuple.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PSIDTuple readPsid(const uint32_t index = 0) const;
+
+ /// @brief Write PSID length / value into a buffer.
+ ///
+ /// @param psid_len PSID length value.
+ /// @param psid PSID value in the range of 0 .. 2^(PSID length).
+ /// @param index buffer index.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value is
+ /// invalid.
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index = 0);
+
/// @brief Read a buffer as string value.
///
/// @param index buffer index.
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
#include <dns/labelsequence.h>
#include <dns/name.h>
#include <util/encode/hex.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
namespace isc {
namespace dhcp {
data_types_["uint32"] = OPT_UINT32_TYPE;
data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+ data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
+ data_types_["psid"] = OPT_PSID_TYPE;
data_types_["string"] = OPT_STRING_TYPE;
data_types_["fqdn"] = OPT_FQDN_TYPE;
data_types_["record"] = OPT_RECORD_TYPE;
data_type_names_[OPT_UINT32_TYPE] = "uint32";
data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+ data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix";
+ data_type_names_[OPT_PSID_TYPE] = "psid";
data_type_names_[OPT_STRING_TYPE] = "string";
data_type_names_[OPT_FQDN_TYPE] = "fqdn";
data_type_names_[OPT_RECORD_TYPE] = "record";
case OPT_IPV6_ADDRESS_TYPE:
return (asiolink::V6ADDRESS_LEN);
+ case OPT_PSID_TYPE:
+ return (3);
+
default:
;
}
}
}
+PrefixTuple
+OptionDataTypeUtil::readPrefix(const std::vector<uint8_t>& buf) {
+ // Prefix typically consists of the prefix length and the
+ // actual value. If prefix length is 0, the buffer length should
+ // be at least 1 byte to hold this length value.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read prefix length from "
+ "a truncated buffer");
+ }
+
+ // Surround everything with try-catch to unify exceptions being
+ // thrown by various functions and constructors.
+ try {
+ // Try to create PrefixLen object from the prefix length held
+ // in the buffer. This may cause an exception if the length is
+ // invalid (greater than 128).
+ PrefixLen prefix_len(buf.at(0));
+
+ // Convert prefix length to bytes, because we operate on bytes,
+ // rather than bits.
+ uint8_t prefix_len_bytes = (prefix_len.asUint8() / 8);
+ // Check if we need to zero pad any bits. This is the case when
+ // the prefix length is not divisible by 8 (bits per byte). The
+ // calculations below may require some explanations. We first
+ // perform prefix_len % 8 to get the number of useful bits beyond
+ // the current prefix_len_bytes value. By substracting it from 8
+ // we get the number of zero padded bits, but with the special
+ // case of 8 when the result of substraction is 0. The value of
+ // 8 really means no padding so we make a modulo division once
+ // again to turn 8s to 0s.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If there are zero padded bits, it means that we need an extra
+ // byte to be retrieved from the buffer.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Make sure that the buffer is long enough. We substract 1 to
+ // also account for the fact that the buffer includes a prefix
+ // length besides a prefix.
+ if ((buf.size() - 1) < prefix_len_bytes) {
+ isc_throw(BadDataTypeCast, "unable to read a prefix having length of "
+ << prefix_len.asUnsigned() << " from a truncated buffer");
+ }
+
+ // It is possible for a prefix to be zero if the prefix length
+ // is zero.
+ IOAddress prefix(IOAddress::IPV6_ZERO_ADDRESS());
+
+ // If there is anything more than prefix length is this buffer
+ // we need to read it.
+ if (buf.size() > 1) {
+ // Buffer has to be copied, because we will modify its
+ // contents by setting certain bits to 0, if necessary.
+ std::vector<uint8_t> prefix_buf(buf.begin() + 1, buf.end());
+ // All further conversions require that the buffer length is
+ // 16 bytes.
+ if (prefix_buf.size() < V6ADDRESS_LEN) {
+ prefix_buf.resize(V6ADDRESS_LEN);
+ if (prefix_len_bytes < prefix_buf.size()) {
+ // Zero all bits in the buffer beyond prefix length
+ // position.
+ std::fill(prefix_buf.begin() + prefix_len_bytes,
+ prefix_buf.end(), 0);
+
+ if (zero_padded_bits) {
+ // There is a byte that require zero padding. We
+ // achieve that by shifting the value of that byte
+ // back and forth by the number of zeroed bits.
+ prefix_buf.at(prefix_len_bytes - 1) =
+ (prefix_buf.at(prefix_len_bytes - 1)
+ >> zero_padded_bits)
+ << zero_padded_bits;
+ }
+ }
+ }
+ // Convert the buffer to the IOAddress object.
+ prefix = IOAddress::fromBytes(AF_INET6, &prefix_buf[0]);
+ }
+
+ return (std::make_pair(prefix_len, prefix));
+
+ } catch (const BadDataTypeCast& ex) {
+ // Pass through the BadDataTypeCast exceptions.
+ throw;
+
+ } catch (const std::exception& ex) {
+ // If an exception of a different type has been thrown, insert
+ // a text that indicates that the failure occurred during reading
+ // the prefix and modify exception type to BadDataTypeCast.
+ isc_throw(BadDataTypeCast, "unable to read a prefix from a buffer: "
+ << ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ std::vector<uint8_t>& buf) {
+ // Prefix must be an IPv6 prefix.
+ if (!prefix.isV6()) {
+ isc_throw(BadDataTypeCast, "illegal prefix value "
+ << prefix);
+ }
+
+ // We don't need to validate the prefix_len value, because it is
+ // already validated by the PrefixLen class.
+ buf.push_back(prefix_len.asUint8());
+
+ // Convert the prefix length to a number of bytes.
+ uint8_t prefix_len_bytes = prefix_len.asUint8() / 8;
+ // Check if there are any bits that require zero padding. See the
+ // commentary in readPrefix to see how this is calculated.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If zero padding is needed it means that we need to extend the
+ // buffer to hold the "partially occupied" byte.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Convert the prefix to byte representation and append it to
+ // our output buffer.
+ std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+ buf.insert(buf.end(), prefix_bytes.begin(),
+ prefix_bytes.begin() + prefix_len_bytes);
+ // If the last byte requires zero padding we achieve that by shifting
+ // bits back and forth by the number of insignificant bits.
+ if (zero_padded_bits) {
+ *buf.rbegin() = (*buf.rbegin() >> zero_padded_bits) << zero_padded_bits;
+ }
+}
+
+PSIDTuple
+OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) {
+ if (buf.size() < 3) {
+ isc_throw(BadDataTypeCast, "unable to read PSID from the buffer."
+ << " Invalid buffer size " << buf.size()
+ << ". Expected 3 bytes (PSID length and PSID value)");
+ }
+
+ // Read PSID length.
+ uint8_t psid_len = buf[0];
+
+ // PSID length must not be greater than 16 bits.
+ if (psid_len > sizeof(uint16_t) * 8) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << static_cast<unsigned>(psid_len)
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ // Read two bytes of PSID value.
+ uint16_t psid = isc::util::readUint16(&buf[1], 2);
+
+ // We need to check that the PSID value does not exceed the maximum value
+ // for a specified PSID length. That means that all bits placed further than
+ // psid_len from the left must be set to 0. So, we create a bit mask
+ // by shifting a value of 0xFFFF to the left and right by psid_len. This
+ // leaves us with psid_len leftmost bits unset and the rest set. Next, we
+ // apply the mask on the PSID value from the buffer and make sure the result
+ // is 0. Otherwise, it means that there are some bits set in the PSID which
+ // aren't supposed to be set.
+ if ((psid_len > 0) &&
+ ((psid & static_cast<uint16_t>(static_cast<uint16_t>(0xFFFF << psid_len)
+ >> psid_len)) != 0)) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid
+ << " for a specified PSID length "
+ << static_cast<unsigned>(psid_len));
+ }
+
+ // All is good, so we can convert the PSID value read from the buffer to
+ // the port set number.
+ psid = psid >> (sizeof(psid) * 8 - psid_len);
+ return (std::make_pair(PSIDLen(psid_len), PSID(psid)));
+}
+
+void
+OptionDataTypeUtil::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf) {
+ if (psid_len.asUint8() > sizeof(psid) * 8) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << psid_len.asUnsigned()
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ if (psid_len.asUint8() > 0 &&
+ (psid.asUint16() > (0xFFFF >> (sizeof(uint16_t) * 8 - psid_len.asUint8())))) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid.asUint16()
+ << " for a specified PSID length "
+ << psid_len.asUnsigned());
+ }
+
+ buf.resize(buf.size() + 3);
+ buf.at(buf.size() - 3) = psid_len.asUint8();
+ isc::util::writeUint16(static_cast<uint16_t>
+ (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())),
+ &buf[buf.size() - 2], 2);
+}
+
+
std::string
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
std::string value;
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
#include <util/io_utilities.h>
#include <stdint.h>
+#include <utility>
namespace isc {
namespace dhcp {
OPT_ANY_ADDRESS_TYPE,
OPT_IPV4_ADDRESS_TYPE,
OPT_IPV6_ADDRESS_TYPE,
+ OPT_IPV6_PREFIX_TYPE,
+ OPT_PSID_TYPE,
OPT_STRING_TYPE,
OPT_FQDN_TYPE,
OPT_RECORD_TYPE,
OPT_UNKNOWN_TYPE
};
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ OptionDataType type; // data type
+ bool array; // is array
+ const OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+ const char* encapsulates; // option space encapsulated by the
+ // particular option.
+};
+
+/// @brief Encapsulation of option definition parameters and the structure size.
+struct OptionDefParamsEncapsulation {
+ const struct OptionDefParams* optionDefParams; // parameters structure
+ const int size; // structure size
+ const char* space; // option space
+};
+
/// @brief Trait class for data types supported in DHCP option definitions.
///
/// This is useful to check whether the type specified as template parameter
static const OptionDataType type = OPT_STRING_TYPE;
};
+/// @brief Encapsulates PSID length.
+class PSIDLen {
+public:
+
+ /// @brief Default constructor.
+ PSIDLen() : psid_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// It checks that the specified value is not greater than
+ /// 16, which is a maximum value for the PSID length.
+ ///
+ /// @param psid_len PSID length.
+ /// @throw isc::OutOfRange If specified PSID length is greater than 16.
+ explicit PSIDLen(const uint8_t psid_len)
+ : psid_len_(psid_len) {
+ if (psid_len_ > sizeof(uint16_t) * 8) {
+ isc_throw(isc::OutOfRange, "invalid value "
+ << asUnsigned() << " of PSID length");
+ }
+ }
+
+ /// @brief Returns PSID length as uint8_t value.
+ uint8_t asUint8() const {
+ return (psid_len_);
+ }
+
+ /// @brief Returns PSID length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. Note that the uint8_t value has to
+ /// be cast to an integer value to be logged as a number. This
+ /// is because the uint8_t is often implemented as char, in which
+ /// case directly loggingan uint8_t value prints a character rather
+ /// than a number.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(psid_len_));
+ }
+
+private:
+
+ /// @brief PSID length.
+ uint8_t psid_len_;
+};
+
+/// @brief Encapsulates PSID value.
+class PSID {
+public:
+
+ /// @brief Default constructor.
+ PSID() : psid_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor doesn't perform any checks on the input data.
+ ///
+ /// @param psid PSID value.
+ explicit PSID(const uint16_t psid)
+ : psid_(psid) {
+ }
+
+ /// @brief Returns PSID value as a number.
+ uint16_t asUint16() const {
+ return (psid_);
+ }
+
+private:
+
+ /// @brief PSID value.
+ uint16_t psid_;
+
+};
+
+/// @brief Defines a pair of PSID length / value.
+typedef std::pair<PSIDLen, PSID> PSIDTuple;
+
+/// @brief Encapsulates prefix length.
+class PrefixLen {
+public:
+
+ /// @brief Default constructor.
+ PrefixLen() : prefix_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor checks if the specified prefix length is
+ /// in the range of 0 to 128.
+ ///
+ /// @param prefix_len Prefix length value.
+ /// @throw isc::OutOfRange If specified prefix length is greater than 128.
+ explicit PrefixLen(const uint8_t prefix_len)
+ : prefix_len_(prefix_len) {
+ }
+
+ /// @brief Returns prefix length as uint8_t value.
+ uint8_t asUint8() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns prefix length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. See @ref PSIDLen::asUnsigned for the
+ /// use cases of this accessor.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(prefix_len_));
+ }
+
+private:
+
+ /// @brief Prefix length.
+ uint8_t prefix_len_;
+};
+
+/// @brief Defines a pair of prefix length / value.
+typedef std::pair<PrefixLen, asiolink::IOAddress> PrefixTuple;
+
/// @brief Utility class for option data types.
///
/// This class provides a set of utility functions to operate on
///
/// @param buf input buffer.
/// @param family address family: AF_INET or AF_INET6.
- ///
+ ///
/// @throw isc::dhcp::BadDataTypeCast when the data being read
/// is truncated.
/// @return address being read.
/// @throw isc::dhcp::BadDataTypeCast if provided name is malformed.
static unsigned int getLabelCount(const std::string& text_name);
+ /// @brief Read prefix from a buffer.
+ ///
+ /// This method reads prefix length and a prefix value from a buffer.
+ /// The prefix value has variable length and this length is determined
+ /// from the first byte of the buffer. If the length is not divisible
+ /// by 8, the prefix is padded with zeros to the next byte boundary.
+ ///
+ /// @param buf input buffer holding a prefix length / prefix tuple.
+ ///
+ /// @return Prefix length and value.
+ static PrefixTuple readPrefix(const std::vector<uint8_t>& buf);
+
+ /// @brief Append prefix into a buffer.
+ ///
+ /// This method writes prefix length (1 byte) followed by a variable
+ /// length prefix.
+ ///
+ /// @param prefix_len Prefix length in bits (0 to 128).
+ /// @param prefix Prefix value.
+ /// @param [out] Output buffer.
+ static void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read PSID length / value tuple from a buffer.
+ ///
+ /// This method reads three bytes from a buffer. The first byte
+ /// holds a PSID length value. The remaining two bytes contain a
+ /// zero padded PSID value.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value held
+ /// in the buffer is incorrect or the buffer is truncated.
+ static PSIDTuple readPsid(const std::vector<uint8_t>& buf);
+
+ /// @brief Append PSID length/value into a buffer.
+ ///
+ /// This method appends 1 byte of PSID length and 2 bytes of PSID
+ /// value into a buffer. The PSID value contains a PSID length
+ /// number of significant bits, followed by 16 - PSID length
+ /// zero bits.
+ ///
+ /// @param psid_len PSID length in the range of 0 to 16 holding the
+ /// number of significant bits within the PSID value.
+ /// @param psid PSID value, where the lowest value is 0, and the
+ /// highest value is 2^(PSID length).
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if specified psid_len or
+ /// psid value is incorrect.
+ static void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf);
+
/// @brief Read string value from a buffer.
///
/// @param buf input buffer.
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_opaque_data_tuples.h>
-#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/dynamic_bitset.hpp>
using namespace std;
using namespace isc::util;
OptionDataTypeUtil::writeAddress(address, buf);
return;
}
+ case OPT_IPV6_PREFIX_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided address/prefix "
+ << value
+ << " is not valid.");
+ }
+
+ std::string txt_address = txt.substr(0, pos);
+ isc::asiolink::IOAddress address = isc::asiolink::IOAddress(txt_address);
+ if (!address.isV6()) {
+ isc_throw(BadDataTypeCast, "provided address "
+ << txt_address
+ << " is not a valid IPv4 or IPv6 address.");
+ }
+
+ std::string txt_prefix = txt.substr(pos + 1);
+ uint8_t len = 0;
+ try {
+ // start with the first character after /
+ len = lexicalCastWithRangeCheck<uint8_t>(txt_prefix);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided prefix "
+ << txt_prefix
+ << " is not valid.");
+ }
+
+
+ // Write a prefix.
+ OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf);
+
+ return;
+ }
+ case OPT_PSID_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided PSID value "
+ << value << " is not valid");
+ }
+
+ const std::string txt_psid = txt.substr(0, pos);
+ const std::string txt_psid_len = txt.substr(pos + 1);
+
+ uint16_t psid = 0;
+ uint8_t psid_len = 0;
+
+ try {
+ psid = lexicalCastWithRangeCheck<uint16_t>(txt_psid);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID "
+ << txt_psid << " is not valid");
+ }
+
+ try {
+ psid_len = lexicalCastWithRangeCheck<uint8_t>(txt_psid_len);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID length "
+ << txt_psid_len << " is not valid");
+ }
+
+ OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf);
+ return;
+ }
case OPT_STRING_TYPE:
OptionDataTypeUtil::writeString(value, buf);
return;
#include <boost/multi_index_container.hpp>
#include <boost/shared_ptr.hpp>
#include <map>
+#include <string>
namespace isc {
namespace dhcp {
/// - "uint16"
/// - "uint32"
/// - "ipv4-address" (IPv4 Address)
-/// - "ipv6-address" (IPV6 Address)
+/// - "ipv6-address" (IPv6 Address)
+/// - "ipv6-prefix" (IPv6 variable length prefix)
+/// - "psid" (PSID length / value)
/// - "string"
/// - "fqdn" (fully qualified name)
/// - "record" (set of data fields of different types)
/// Pointer to an option definition container.
typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+/// Container that holds option definitions for various option spaces.
+typedef std::map<std::string, OptionDefContainerPtr> OptionDefContainers;
+
/// Container that holds various vendor option containers
typedef std::map<uint32_t, OptionDefContainerPtr> VendorOptionDefContainers;
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
#include <util/io_utilities.h>
#include <stdint.h>
if (!OptionDataTypeTraits<T>::integer_type) {
isc_throw(dhcp::InvalidDataType, "non-integer type");
}
- setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
+ setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
}
/// @brief Constructor.
if (!OptionDataTypeTraits<T>::integer_type) {
isc_throw(dhcp::InvalidDataType, "non-integer type");
}
- setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
+ setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
unpack(begin, end);
}
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
#include <stdint.h>
#include <string>
-#define DHCP4_OPTION_SPACE "dhcp4"
-#define DHCP6_OPTION_SPACE "dhcp6"
+#define DHCP4_OPTION_SPACE "dhcp4"
+#define DHCP6_OPTION_SPACE "dhcp6"
+#define MAPE_V6_OPTION_SPACE "s46-cont-mape-options"
+#define MAPT_V6_OPTION_SPACE "s46-cont-mapt-options"
+#define LW_V6_OPTION_SPACE "s46-cont-lw-options"
+#define V4V6_RULE_OPTION_SPACE "s46-rule-options"
+#define V4V6_BIND_OPTION_SPACE "s46-v4v6bind-options"
namespace isc {
namespace dhcp {
void setVendorSpace(const uint32_t enterprise_number);
private:
-
+
uint32_t enterprise_number_; ///< IANA assigned enterprise number.
};
// a vector as an input.
buffer_in.readVector(opts_buffer, opts_len);
- size_t offset = LibDHCP::unpackOptions4(opts_buffer, "dhcp4", options_);
+ size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_);
// If offset is not equal to the size and there is no DHO_END,
// then something is wrong here. We either parsed past input
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
+#include <dhcp/option_space.h>
#include <dhcp/option_vendor_class.h>
#include <dhcp/option_vendor.h>
#include <dhcp/pkt6.h>
// If custom option parsing function has been set, use this function
// to parse options. Otherwise, use standard function from libdhcp.
- size_t offset = LibDHCP::unpackOptions6(opt_buffer, "dhcp6", options_);
+ size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_);
// If offset is not equal to the size, then something is wrong here. We
// either parsed past input buffer (bug in our code) or we haven't parsed
// If custom option parsing function has been set, use this function
// to parse options. Otherwise, use standard function from libdhcp.
- LibDHCP::unpackOptions6(opt_buffer, "dhcp6", relay.options_,
+ LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, relay.options_,
&relay_msg_offset, &relay_msg_len);
/// @todo: check that each option appears at most once
#include <dhcp/option_data_types.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
namespace isc {
namespace dhcp {
#define NO_RECORD_DEF 0, 0
#endif
-/// @brief Parameters being used to make up an option definition.
-struct OptionDefParams {
- const char* name; // option name
- uint16_t code; // option code
- OptionDataType type; // data type
- bool array; // is array
- const OptionDataType* records; // record fields
- size_t records_size; // number of fields in a record
- const char* encapsulates; // option space encapsulated by
- // the particular option.
-};
-
// fqdn option record fields.
//
// Note that the flags field indicates the type of domain
RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
/// @brief Definitions of standard DHCPv4 options.
-const OptionDefParams OPTION_DEF_PARAMS4[] = {
+const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
{ "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "time-offset", DHO_TIME_OFFSET, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" },
{ "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
};
/// Number of option definitions defined.
-const int OPTION_DEF_PARAMS_SIZE4 =
- sizeof(OPTION_DEF_PARAMS4) / sizeof(OPTION_DEF_PARAMS4[0]);
-
+const int STANDARD_V4_OPTION_DEFINITIONS_SIZE =
+ sizeof(STANDARD_V4_OPTION_DEFINITIONS) / sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]);
/// Start Definition of DHCPv6 options
RECORD_DECL(LQ_RELAY_DATA_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
// remote-id
RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// s46-rule
+RECORD_DECL(S46_RULE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-v4v6bind
+RECORD_DECL(S46_V4V6BIND, OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-portparams
+RECORD_DECL(S46_PORTPARAMS, OPT_UINT8_TYPE, OPT_PSID_TYPE);
// status-code
RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
// vendor-class
/// This however does not work on Solaris (GCC) which issues a
/// warning about lack of initializers for some struct members
/// causing build to fail.
-const OptionDefParams OPTION_DEF_PARAMS6[] = {
+const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
{ "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS), "" },
{ "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
NO_RECORD_DEF, "" },
{ "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },
+ { "pd-exclude", D6O_PD_EXCLUDE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
NO_RECORD_DEF, "" },
{ "dhcpv4-message", D6O_DHCPV4_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "signature", D6O_SIGNATURE, OPT_RECORD_TYPE, false,
RECORD_DEF(SIGNATURE_RECORDS), "" },
{ "timestamp", D6O_TIMESTAMP, OPT_BINARY_TYPE, false,
- NO_RECORD_DEF, "" }
+ NO_RECORD_DEF, "" },
+ { "aftr-name", D6O_AFTR_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "s46-cont-mape", D6O_S46_CONT_MAPE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+ MAPE_V6_OPTION_SPACE },
+ { "s46-cont-mapt", D6O_S46_CONT_MAPT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+ MAPT_V6_OPTION_SPACE },
+ { "s46-cont-lw", D6O_S46_CONT_LW, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+ LW_V6_OPTION_SPACE }
// @todo There is still a bunch of options for which we have to provide
// definitions but we don't do it because they are not really
};
/// Number of option definitions defined.
-const int OPTION_DEF_PARAMS_SIZE6 =
- sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+const int STANDARD_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(STANDARD_V6_OPTION_DEFINITIONS) /
+ sizeof(STANDARD_V6_OPTION_DEFINITIONS[0]);
+
+// Option definitions that belong to two or more option spaces are defined here.
+const OptionDefParams OPTION_DEF_PARAMS_S46_BR = { "s46-br", D6O_S46_BR,
+ OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" };
+const OptionDefParams OPTION_DEF_PARAMS_S46_RULE = { "s46-rule", D6O_S46_RULE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE };
+const OptionDefParams OPTION_DEF_PARAMS_S46_PORTPARAMS = { "s46-portparams",
+ D6O_S46_PORTPARAMS, OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" };
/// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC.
/// 4o6-* options are used for inter-process communication. For details, see
///
/// @todo: As those options are defined by ISC, they do not belong in std_option_defs.h.
/// We need to move them to a separate file, e.g. isc_option_defs.h
-const OptionDefParams ISC_V6_DEFS[] = {
- { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
- { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }
+const OptionDefParams ISC_V6_OPTION_DEFINITIONS[] = {
+ { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE,
+ false, NO_RECORD_DEF, "" }
+};
+
+const int ISC_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(ISC_V6_OPTION_DEFINITIONS) /
+ sizeof(ISC_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPE option definitions
+const OptionDefParams MAPE_V6_OPTION_DEFINITIONS[] = {
+ OPTION_DEF_PARAMS_S46_BR,
+ OPTION_DEF_PARAMS_S46_RULE
+};
+
+const int MAPE_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(MAPE_V6_OPTION_DEFINITIONS) /
+ sizeof(MAPE_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPT option definitions
+const OptionDefParams MAPT_V6_OPTION_DEFINITIONS[] = {
+ OPTION_DEF_PARAMS_S46_RULE,
+ { "s46-dmr", D6O_S46_DMR, OPT_IPV6_PREFIX_TYPE, false, NO_RECORD_DEF, "" }
+};
+
+const int MAPT_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(MAPT_V6_OPTION_DEFINITIONS) /
+ sizeof(MAPT_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief LW option definitions
+const OptionDefParams LW_V6_OPTION_DEFINITIONS[] = {
+ OPTION_DEF_PARAMS_S46_BR,
+ { "s46-v4v6bind", D6O_S46_V4V6BIND, OPT_RECORD_TYPE, false,
+ RECORD_DEF(S46_V4V6BIND), V4V6_BIND_OPTION_SPACE }
+};
+
+const int LW_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(LW_V6_OPTION_DEFINITIONS) /
+ sizeof(LW_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief Rule option definitions
+const OptionDefParams V4V6_RULE_OPTION_DEFINITIONS[] = {
+ OPTION_DEF_PARAMS_S46_PORTPARAMS
+};
+
+const int V4V6_RULE_OPTION_DEFINITIONS_SIZE =
+ sizeof(V4V6_RULE_OPTION_DEFINITIONS) /
+ sizeof(V4V6_RULE_OPTION_DEFINITIONS[0]);
+
+/// @brief Bind option definitions
+const OptionDefParams V4V6_BIND_OPTION_DEFINITIONS[] = {
+ OPTION_DEF_PARAMS_S46_PORTPARAMS
};
-const int ISC_V6_DEFS_SIZE = sizeof(ISC_V6_DEFS) / sizeof(OptionDefParams);
+const int V4V6_BIND_OPTION_DEFINITIONS_SIZE =
+ sizeof(V4V6_BIND_OPTION_DEFINITIONS) /
+ sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]);
} // unnamed namespace
libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
libdhcp___unittests_SOURCES += option6_iaprefix_unittest.cc
+libdhcp___unittests_SOURCES += option6_pdexclude_unittest.cc
libdhcp___unittests_SOURCES += option6_status_code_unittest.cc
libdhcp___unittests_SOURCES += option_int_unittest.cc
libdhcp___unittests_SOURCES += option_int_array_unittest.cc
const std::type_info& expected_type,
const std::string& encapsulates = "") {
// Use V4 universe.
- testStdOptionDefs(Option::V4, code, begin, end, expected_type,
- encapsulates);
+ testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end,
+ expected_type, encapsulates);
}
/// @brief Test DHCPv6 option definition.
const std::type_info& expected_type,
const std::string& encapsulates = "") {
// Use V6 universe.
- testStdOptionDefs(Option::V6, code, begin, end, expected_type,
- encapsulates);
+ testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin,
+ end, expected_type, encapsulates);
+ }
+
+ /// @brief Test DHCPv6 option definition in a given option space.
+ ///
+ /// This function tests if option definition for an option from a
+ /// given option space has been initialized correctly.
+ ///
+ /// @param option_space option space.
+ /// @param code option code.
+ /// @param begin iterator pointing at beginning of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing at end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
+ static void testOptionDefs6(const std::string& option_space,
+ const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
+ testStdOptionDefs(Option::V6, option_space, code, begin,
+ end, expected_type, encapsulates);
}
/// @brief Create a sample DHCPv4 option 43 with suboptions.
/// This function tests if option definition for standard
/// option has been initialized correctly.
///
+ /// @param option_space option space.
/// @param code option code.
/// @param begin iterator pointing at beginning of a buffer to
/// be used to create option instance.
/// factory function returned by the option definition.
/// @param encapsulates name of the option space being encapsulated
/// by the option.
- static void testStdOptionDefs(const Option::Universe u,
+ static void testStdOptionDefs(const Option::Universe& u,
+ const std::string& option_space,
const uint16_t code,
const OptionBufferConstIter begin,
const OptionBufferConstIter end,
// the definition for a particular option code.
// We don't have to initialize option definitions here because they
// are initialized in the class's constructor.
- OptionDefContainerPtr options = LibDHCP::getOptionDefs(u);
+ OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space);
// Get the container index #1. This one allows for searching
// option definitions using option code.
const OptionDefContainerTypeIndex& idx = options->get<1>();
// Get the option definition for RAI option. This option is represented
// by OptionCustom which requires a definition to be passed to
// the constructor.
- OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(Option::V4,
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
DHO_DHCP_AGENT_OPTIONS);
ASSERT_TRUE(rai_def);
// Create RAI option.
EXPECT_EQ(0x0, option_bar->getValue());
}
-TEST_F(LibDhcpTest, isStandardOption4) {
- // Get all option codes that are not occupied by standard options.
- const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
- 109, 110, 111, 115, 126, 127, 147, 148, 149,
- 178, 179, 180, 181, 182, 183, 184, 185, 186,
- 187, 188, 189, 190, 191, 192, 193, 194, 195,
- 196, 197, 198, 199, 200, 201, 202, 203, 204,
- 205, 206, 207, 214, 215, 216, 217, 218, 219,
- 222, 223, 224, 225, 226, 227, 228, 229, 230,
- 231, 232, 233, 234, 235, 236, 237, 238, 239,
- 240, 241, 242, 243, 244, 245, 246, 247, 248,
- 249, 250, 251, 252, 253, 254 };
- const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
-
- // Try all possible option codes.
- for (size_t i = 0; i < 256; ++i) {
- // Some ranges of option codes are unassigned and thus the isStandardOption
- // should return false for them.
- bool check_unassigned = false;
- // Check the array of unassigned options to find out whether option code
- // is assigned to standard option or unassigned.
- for (size_t j = 0; j < unassigned_num; ++j) {
- // If option code is found within the array of unassigned options
- // we the isStandardOption function should return false.
- if (unassigned_codes[j] == i) {
- check_unassigned = true;
- EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
- unassigned_codes[j]))
- << "Test failed for option code " << unassigned_codes[j];
- break;
- }
- }
- // If the option code belongs to the standard option then the
- // isStandardOption should return true.
- if (!check_unassigned) {
- EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
- << "Test failed for the option code " << i;
- }
- }
-}
-
-TEST_F(LibDhcpTest, isStandardOption6) {
- // All option codes in the range from 0 to 78 (except 10 and 35)
- // identify the standard options.
- for (uint16_t code = 0; code < 79; ++code) {
- if (code != 10 && code != 35) {
- EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
- << "Test failed for option code " << code;
- }
- }
-
- // Check the option codes 10 and 35. They are unassigned.
- EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
- EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
-
- // Check a range of option codes above 78. Those are option codes
- // identifying non-standard options.
- for (uint16_t code = 79; code < 512; ++code) {
- EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
- << "Test failed for option code " << code;
- }
-}
-
TEST_F(LibDhcpTest, stdOptionDefs4) {
// Create a buffer that holds dummy option data.
LibDhcpTest::testStdOptionDefs6(D6O_TIMESTAMP, begin, begin + 8,
typeid(Option));
+
+ // RFC7598 options
+ LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+ typeid(OptionCustom), "s46-rule-options");
+ LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+ typeid(OptionCustom), "s46-rule-options");
+ LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin,
+ end, typeid(OptionCustom),
+ "s46-v4v6bind-options");
+ LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS,
+ begin, end, typeid(OptionCustom), "");
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end,
+ typeid(OptionCustom),
+ "s46-cont-mape-options");
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end,
+ typeid(OptionCustom),
+ "s46-cont-mapt-options");
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end,
+ typeid(OptionCustom),
+ "s46-cont-lw-options");
+
}
// This test checks if the DHCPv6 option definition can be searched by
// an option name.
TEST_F(LibDhcpTest, getOptionDefByName6) {
// Get all definitions.
- const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(Option::V6);
+ const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE);
// For each definition try to find it using option name.
for (OptionDefContainer::const_iterator def = defs->begin();
def != defs->end(); ++def) {
OptionDefinitionPtr def_by_name =
- LibDHCP::getOptionDef(Option::V6, (*def)->getName());
+ LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName());
ASSERT_TRUE(def_by_name);
ASSERT_TRUE(**def == *def_by_name);
}
// an option name.
TEST_F(LibDhcpTest, getOptionDefByName4) {
// Get all definitions.
- const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(Option::V4);
+ const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE);
// For each definition try to find it using option name.
for (OptionDefContainer::const_iterator def = defs->begin();
def != defs->end(); ++def) {
OptionDefinitionPtr def_by_name =
- LibDHCP::getOptionDef(Option::V4, (*def)->getName());
+ LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName());
ASSERT_TRUE(def_by_name);
ASSERT_TRUE(**def == *def_by_name);
}
--- /dev/null
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// Author: Andrei Pavel <andrei.pavel@qualitance.com>
+//
+// 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcpsrv/pool.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace asiolink;
+
+namespace {
+
+// Prefix constants used in unit tests.
+const IOAddress v4("192.0.2.0");
+const IOAddress bee0("2001:db8:dead:bee0::");
+const IOAddress beef("2001:db8:dead:beef::");
+const IOAddress cafe("2001:db8:dead:cafe::");
+const IOAddress beef01("2001:db8:dead:beef::01");
+
+// This test verifies that the constructor sets parameters appropriately.
+TEST(Option6PDExcludeTest, constructor) {
+ Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60);
+
+ EXPECT_EQ(beef, option.getDelegatedPrefix());
+ EXPECT_EQ(56, option.getDelegatedPrefixLength());
+ EXPECT_EQ(beef01, option.getExcludedPrefix());
+ EXPECT_EQ(60, option.getExcludedPrefixLength());
+ // Total length is a sum of option header length, excluded prefix
+ // length (always 1 byte) and delegated prefix length - excluded prefix
+ // length rounded to bytes.
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len());
+
+ // v4 prefix is not accepted.
+ EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue);
+ EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue);
+ // Length greater than 128 is not accepted.
+ EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue);
+ // Excluded prefix length must be greater than delegated prefix length.
+ EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue);
+ // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2).
+ EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue);
+}
+
+// This test verifies that on-wire format of the Prefix Exclude option is
+// created properly.
+TEST(Option6PDExcludeTest, pack) {
+ // Expected wire format of the option.
+ const uint8_t expected_data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length 2
+ 0x3F, 0x70 // excluded prefix length 59 + subnet id
+ };
+ std::vector<uint8_t> expected_vec(expected_data,
+ expected_data + sizeof(expected_data));
+ // Generate wire format of the option.
+ util::OutputBuffer buf(128);
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+ 59,
+ IOAddress("2001:db8:dead:beef::"),
+ 63)));
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Check that size matches.
+ ASSERT_EQ(expected_vec.size(), buf.getLength());
+
+ // Check that the generated wire format is correct.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ std::vector<uint8_t> vec(data, data + buf.getLength());
+ ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 byte.
+TEST(Option6PDExcludeTest, unpack1ByteSubnetId) {
+ const uint8_t data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length 2
+ 0x40, 0x78 // excluded prefix length 60 + subnet id
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Parse option.
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::1"),
+ 59, vec.begin() + 4, vec.end()))
+ );
+
+ // Make sure that the option has been parsed correctly.
+ EXPECT_EQ("2001:db8:dead:bee0::1", option->getDelegatedPrefix().toText());
+ EXPECT_EQ(59, static_cast<int>(option->getDelegatedPrefixLength()));
+ EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+ EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 bytes.
+TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
+ const uint8_t data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length
+ 0x40, 0xbe, 0xef // excluded prefix length 60 + subnet id
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Parse option.
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6PDExclude(IOAddress("2001:db8:dead::"),
+ 48, vec.begin() + 4, vec.end()))
+ );
+
+ // Make sure that the option has been parsed correctly.
+ EXPECT_EQ("2001:db8:dead::", option->getDelegatedPrefix().toText());
+ EXPECT_EQ(48, static_cast<int>(option->getDelegatedPrefixLength()));
+ EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+ EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies that errors are reported when option buffer contains
+// invalid option data.
+TEST(Option6PDExcludeTest, unpackErrors) {
+ const uint8_t data[] = {
+ 0x00, 0x43,
+ 0x00, 0x02,
+ 0x40, 0x78
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Option has no IPv6 subnet id.
+ EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+ 59, vec.begin() + 4, vec.end() - 1),
+ BadValue);
+
+ // Option has IPv6 subnet id of 1 byte, but it should have 2 bytes.
+ EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead::"), 48,
+ vec.begin() + 4, vec.end()),
+ BadValue);
+}
+
+// This test verifies conversion of the Prefix Exclude option to the
+// textual format.
+TEST(Option6PDExcludeTest, toText) {
+ Option6PDExclude option(bee0, 59, beef, 64);
+ EXPECT_EQ("type=00067, len=00002: 2001:db8:dead:beef::/64",
+ option.toText());
+}
+
+// This test verifies calculation of the Prefix Exclude option length.
+TEST(Option6PDExcludeTest, len) {
+ Option6PDExcludePtr option;
+ // The IPv6 subnet id is 2 bytes long. Hence the total length is
+ // 2 bytes (option code) + 2 bytes (option length) + 1 byte
+ // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes.
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64)));
+ EXPECT_EQ(7, option->len());
+
+ // IPv6 subnet id is 1 byte long. The total length is 6.
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64)));
+ EXPECT_EQ(6, option->len());
+}
+
+} // anonymous namespace
namespace {
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
/// @brief OptionCustomTest test class.
class OptionCustomTest : public ::testing::Test {
public:
);
}
+// The purpose of this test is to verify that the option definition comprising
+// single variable length prefix can be used to create an instance of custom
+// option.
+TEST_F(OptionCustomTest, prefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(32, buf);
+ writeInt<uint32_t>(0x30000001, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a prefix.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ // Read prefix from buffer #0.
+ ASSERT_NO_THROW(prefix = option->readPrefix(0));
+
+ // The prefix comprises a prefix length and prefix value.
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000:1::", prefix.second.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single PSID can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, psidData) {
+ OptionDefinition opt_def("option-foo", 1000, "psid",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint16_t>(0x8000, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a PSID length / PSID value tuple.
+ PSIDTuple psid;
+ // Read PSID length / PSID value from buffer #0.
+ ASSERT_NO_THROW(psid = option->readPsid(0));
+
+ // The PSID comprises a PSID length and PSID value.
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(0x08, psid.second.asUint16());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
// The purpose of this test is to verify that the option definition comprising
// string value can be used to create an instance of custom option.
EXPECT_EQ("example.com.", domain1);
}
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 prefixes can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, prefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+ // The following buffer comprises three prefixes with different
+ // prefix lengths.
+ const uint8_t data[] = {
+ 32, 0x30, 0x01, 0x00, 0x01, // 3001:1::/32
+ 16, 0x30, 0x00, // 3000::/16
+ 48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01 // 2001:db8:1::/48
+ };
+
+ // Initialize input buffer
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 prefixes.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PrefixTuple prefix0(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix1(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix2(ZERO_PREFIX_TUPLE);
+
+ ASSERT_NO_THROW(prefix0 = option->readPrefix(0));
+ ASSERT_NO_THROW(prefix1 = option->readPrefix(1));
+ ASSERT_NO_THROW(prefix2 = option->readPrefix(2));
+
+ EXPECT_EQ(32, prefix0.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix0.second.toText());
+
+ EXPECT_EQ(16, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix2.second.toText());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of PSIDs can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, psidDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+ // The following buffer comprises three PSIDs.
+ const uint8_t data[] = {
+ 4, 0x80, 0x00, // PSID len = 4, PSID = '1000 000000000000b'
+ 6, 0xD4, 0x00, // PSID len = 6, PSID = '110101 0000000000b'
+ 1, 0x80, 0x00 // PSID len = 1, PSID = '1 000000000000000b'
+ };
+ // Initialize input buffer.
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 PSIDs.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ ASSERT_NO_THROW(psid0 = option->readPsid(0));
+ ASSERT_NO_THROW(psid1 = option->readPsid(1));
+ ASSERT_NO_THROW(psid2 = option->readPsid(2));
+
+ // PSID value is equal to '1000b' (8).
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(0x08, psid0.second.asUint16());
+
+ // PSID value is equal to '110101b' (0x35)
+ EXPECT_EQ(6, psid1.first.asUnsigned());
+ EXPECT_EQ(0x35, psid1.second.asUint16());
+
+ // PSID value is equal to '1b' (1).
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(0x01, psid2.second.asUint16());
+}
+
// The purpose of this test is to verify that the opton definition comprising
// a record of fixed-size fields can be used to create an option with a
// suboption.
ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
const char fqdn_data[] = {
writeAddress(IOAddress("192.168.0.1"), buf);
// Initialize field 4 to IPv6 address.
writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize PSID len and PSID value.
+ writeInt<uint8_t>(6, buf);
+ writeInt<uint16_t>(0xD400, buf);
// Initialize field 5 to string value.
writeString("ABCD", buf);
ASSERT_TRUE(option);
// We should have 6 data fields.
- ASSERT_EQ(6, option->getDataFieldsNum());
+ ASSERT_EQ(7, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
EXPECT_EQ("2001:db8:1::1", value4.toText());
// Verify value in the field 5.
- std::string value5;
- ASSERT_NO_THROW(value5 = option->readString(5));
- EXPECT_EQ("ABCD", value5);
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(6, value5.first.asUnsigned());
+ EXPECT_EQ(0x35, value5.second.asUint16());
+
+ // Verify value in the field 6.
+ std::string value6;
+ ASSERT_NO_THROW(value6 = option->readString(6));
+ EXPECT_EQ("ABCD", value6);
}
// The purpose of this test is to verify that truncated buffer
EXPECT_EQ("2001:db8:1::1", address.toText());
}
+// The purpose of this test is to verify that an option comprising
+// a prefix can be created and that the prefix can be overriden by
+// a new value.
+TEST_F(OptionCustomTest, setPrefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Make sure the default prefix is set.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ // Write prefix.
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::")));
+
+ // Read prefix back and make sure it is the one we just set.
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(48, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// a single PSID can be created and that the PSID can be overriden
+// by a new value.
+TEST_F(OptionCustomTest, setPsidData) {
+ OptionDefinition opt_def("option-foo", 1000, "psid");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Make sure the default PSID is set.
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ // Write PSID.
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8)));
+
+ // Read PSID back and make sure it is the one we just set.
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(8, psid.second.asUint16());
+}
+
// The purpose of this test is to verify that an option comprising
// single string value can be created and that this value
// is initialized to the default value. Also, this test checks that
);
}
+/// The purpose of this test is to verify that an option comprising an
+/// array of PSIDs can be created with no PSIDs and that PSIDs can be
+/// later added after the option has been created.
+TEST_F(OptionCustomTest, setPSIDPrefixArray) {
+ OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new PSIDs
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1)));
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PSIDTuple psid0 = option->readPsid(0);
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(1, psid0.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid1 = option->readPsid(1);
+ EXPECT_EQ(0, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid2 = option->readPsid(2);
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(1, psid2.second.asUint16());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of IPv6 prefixes can be created with no prefixes and that
+/// prefixes can be later added after the option has been created.
+TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 prefixes into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64),
+ IOAddress("2001:db8:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32),
+ IOAddress("3001:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16),
+ IOAddress("3000::")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option->readPrefix(2);
+ EXPECT_EQ(16, prefix2.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix2.second.toText());
+ });
+}
+
TEST_F(OptionCustomTest, setRecordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
// Create an option and let the data field be initialized
// The number of elements should be equal to number of elements
// in the record.
- ASSERT_EQ(6, option->getDataFieldsNum());
+ ASSERT_EQ(8, option->getDataFieldsNum());
// Check that the default values have been correctly set.
uint16_t value0;
IOAddress value4("2001:db8:1::1");
ASSERT_NO_THROW(value4 = option->readAddress(4));
EXPECT_EQ("::", value4.toText());
- std::string value5 = "xyz";
- ASSERT_NO_THROW(value5 = option->readString(5));
- EXPECT_TRUE(value5.empty());
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(0, value5.first.asUnsigned());
+ EXPECT_EQ(0, value5.second.asUint16());
+ PrefixTuple value6(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(0, value6.first.asUnsigned());
+ EXPECT_EQ("::", value6.second.toText());
+ std::string value7 = "xyz";
+ ASSERT_NO_THROW(value7 = option->readString(7));
+ EXPECT_TRUE(value7.empty());
// Override each value with a new value.
ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
- ASSERT_NO_THROW(option->writeString("hello world", 5));
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
+ IOAddress("2001:db8:1::"), 6));
+ ASSERT_NO_THROW(option->writeString("hello world", 7));
// Check that the new values have been correctly set.
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
EXPECT_EQ("192.168.0.1", value3.toText());
ASSERT_NO_THROW(value4 = option->readAddress(4));
EXPECT_EQ("2001:db8:1::100", value4.toText());
- ASSERT_NO_THROW(value5 = option->readString(5));
- EXPECT_EQ(value5, "hello world");
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(4, value5.first.asUnsigned());
+ EXPECT_EQ(8, value5.second.asUint16());
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(48, value6.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", value6.second.toText());
+ ASSERT_NO_THROW(value7 = option->readString(7));
+ EXPECT_EQ(value7, "hello world");
}
// The purpose of this test is to verify that pack function for
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 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
#include <config.h>
#include <dhcp/option_data_types.h>
#include <gtest/gtest.h>
+#include <utility>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
namespace {
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
/// @brief Test class for option data type utilities.
class OptionDataTypesTest : public ::testing::Test {
public:
);
}
+// The purpose of this test is to verify that the variable length prefix
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readPrefix) {
+ std::vector<uint8_t> buf;
+
+ // Prefix 2001:db8::/64
+ writeInt<uint8_t>(64, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8::/63
+ writeInt<uint8_t>(63, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(63, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8:c0000. Note that the last four bytes are filled with
+ // 0xFF (all bits set). When the prefix is read those non-significant
+ // bits (beyond prefix length) should be ignored (read as 0). Only first
+ // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF.
+ writeInt<uint8_t>(34, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0xFFFFFFFF, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(34, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:c000::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a length of 0.
+ writeInt<uint8_t>(0, buf);
+ writeInt<uint16_t>(0x2001, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ writeInt<uint8_t>(128, buf);
+ buf.insert(buf.end(), 16, 0x11);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(128, prefix.first.asUnsigned());
+ EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111",
+ prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix length is greater than 128. This should result in an
+ // error.
+ writeInt<uint8_t>(129, buf);
+ writeInt<uint16_t>(0x3000, buf);
+ buf.resize(17);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer truncated. Prefix length of 10 requires at least 2 bytes,
+ // but there is only one byte.
+ writeInt<uint8_t>(10, buf);
+ writeInt<uint8_t>(1, buf);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the variable length prefix
+// is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePrefix) {
+ // Initialize a buffer and store some value in it. We'll want to make
+ // sure that the prefix being written will not override this value, but
+ // will rather be appended.
+ std::vector<uint8_t> buf(1, 1);
+
+ // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because
+ // there are only 34 significant bits. All other bits must be zeroed.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(7, buf.size());
+
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(34, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x20, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x01, static_cast<unsigned>(buf[3]));
+ EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4]));
+ EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5]));
+ EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6]));
+
+ buf.clear();
+
+ // Prefix length is 0. The entire prefix should be ignored.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128),
+ IOAddress("2001:db8::FF"),
+ buf));
+
+ // We should now have a 17 bytes long buffer. 1 byte goes for a prefix
+ // length field, the remaining ones hold the prefix.
+ ASSERT_EQ(17, buf.size());
+ // Because the prefix is 16 bytes long, we can simply use the
+ // IOAddress convenience function to read it back and compare
+ // it with the textual representation. This is simpler than
+ // comparing each byte separately.
+ IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]);
+ EXPECT_EQ("2001:db8::ff", prefix_read.toText());
+
+ buf.clear();
+
+ // It is illegal to use IPv4 address as prefix.
+ EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4),
+ IOAddress("10.0.0.1"), buf),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the
+// PSID-len/PSID tuple can be read from a buffer.
+TEST_F(OptionDataTypesTest, readPsid) {
+ std::vector<uint8_t> buf;
+
+ // PSID length is 6 (bits)
+ writeInt<uint8_t>(6, buf);
+ // 0xA400 is represented as 1010010000000000b, which is equivalent
+ // of portset 0x29 (101001b).
+ writeInt<uint16_t>(0xA400, buf);
+
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(0x29, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length is 0, in which case PSID should be ignored.
+ writeInt<uint8_t>(0, buf);
+ // Let's put some junk into the PSID field to make sure it will
+ // be ignored.
+ writeInt<uint16_t>(0x1234, buf);
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length greater than 16 is not allowed.
+ writeInt<uint8_t>(17, buf);
+ writeInt<uint16_t>(0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // PSID length is 3 bits, but the PSID value is 11 (1011b), so it
+ // is encoded on 4 bits, rather than 3.
+ writeInt<uint8_t>(3, buf);
+ writeInt<uint16_t>(0xB000, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer is truncated - 2 bytes instead of 3.
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint8_t>(0xF0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the PSID-len/PSID
+// tuple is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePsid) {
+ // Let's create a buffer with some data in it. We want to make
+ // sure that the existing data remain untouched when we write
+ // PSID to the buffer.
+ std::vector<uint8_t> buf(1, 1);
+ // PSID length is 4 (bits), PSID value is 8.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf));
+ ASSERT_EQ(4, buf.size());
+ // The byte which existed in the buffer should still hold the
+ // same value.
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ // PSID length should be written as specified in the function call.
+ EXPECT_EQ(4, static_cast<unsigned>(buf[1]));
+ // The PSID structure is as follows:
+ // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code
+ // the PSID. "P" are zero padded bits. The PSID value 8 is coded
+ // on four useful bits as '1000b'. That means that the PSID value
+ // encoded in the PSID field is: '1000000000000000b', which is
+ // 0x8000. The next two EXPECT_EQ statements verify that.
+ EXPECT_EQ(0x80, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[3]));
+
+ // Clear the buffer to make sure we don't append to the
+ // existing data.
+ buf.clear();
+
+ // The PSID length of 0 causes the PSID value (of 6) to be ignored.
+ // As a result, the buffer should hold only zeros.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf));
+ ASSERT_EQ(3, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[2]));
+
+ buf.clear();
+
+ // Another test case, to verify that we can use the maximum length
+ // of PSID (16 bits).
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf));
+ ASSERT_EQ(3, buf.size());
+ // PSID length should be written with no change.
+ EXPECT_EQ(16, static_cast<unsigned>(buf[0]));
+ // Check PSID value.
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x05, static_cast<unsigned>(buf[2]));
+
+ // PSID length of 17 exceeds the maximum allowed value of 16.
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf),
+ OutOfRange);
+
+ // PSID length is 1, which allows for coding up to two (2^1)
+ // port sets. These are namely port set 0 and port set 1. The
+ // value of 2 is out of that range.
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(1), PSID(2), buf),
+ BadDataTypeCast);
+}
+
// The purpose of this test is to verify that the string
// can be read from a buffer correctly.
TEST_F(OptionDataTypesTest, readString) {
// see if it rejects it.
OptionBuffer buf(1);
EXPECT_THROW(
- OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, "dhcp6",
+ OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE,
buf.begin(), buf.end()),
isc::dhcp::InvalidDataType
);
EXPECT_FALSE(opt_def_invalid.haveClientFqdnFormat());
}
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, prefix) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+ // Create a buffer holding a prefix.
+ OptionBuffer buf;
+ buf.push_back(32);
+ buf.push_back(0x30);
+ buf.push_back(0x00);
+ buf.resize(5);
+
+ OptionPtr option_v6;
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and that the instance of this option can be
+// created by specifying the prefix in the textual format.
+TEST_F(OptionDefinitionTest, prefixTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+ OptionPtr option_v6;
+ // Specify a single prefix.
+ std::vector<std::string> values(1, "2001:db8:1::/64");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with an array
+// of IPv6 prefixes can be created and that the instance of this
+// option can be created by specifying multiple prefixes in the
+// textual format.
+TEST_F(OptionDefinitionTest, prefixArrayTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 prefixes
+ std::vector<std::string> values;
+ values.push_back("2001:db8:1:: /64");
+ values.push_back("3000::/ 32");
+ values.push_back("3001:1:: / 48");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 prefixes in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option_cast_v6->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option_cast_v6->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option_cast_v6->readPrefix(2);
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix2.second.toText());
+ });
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, psid) {
+ OptionDefinition opt_def("option-psid", 1000, "psid");
+
+ OptionPtr option_v6;
+
+ // Create a buffer holding PSID.
+ OptionBuffer buf;
+ buf.push_back(6);
+ buf.push_back(0x4);
+ buf.push_back(0x0);
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(1, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and that the instance of this option can be
+// created by specifying PSID length and value in the textual format.
+TEST_F(OptionDefinitionTest, psidTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "psid");
+
+ OptionPtr option_v6;
+ // Specify a single PSID with a length of 6 and value of 3.
+ std::vector<std::string> values(1, "3 / 6");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(3, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with an array
+// of PSIDs can be created and that the instance of this option can be
+// created by specifying multiple PSIDs in the textual format.
+TEST_F(OptionDefinitionTest, psidArrayTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "psid", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 PSIDs.
+ std::vector<std::string> values;
+ values.push_back("3 / 6");
+ values.push_back("0/1");
+ values.push_back("7 / 3");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 PSIDs in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ // Check their values.
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ psid0 = option_cast_v6->readPsid(0);
+ EXPECT_EQ(6, psid0.first.asUnsigned());
+ EXPECT_EQ(3, psid0.second.asUint16());
+
+ psid1 = option_cast_v6->readPsid(1);
+ EXPECT_EQ(1, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+
+ psid2 = option_cast_v6->readPsid(2);
+ EXPECT_EQ(3, psid2.first.asUnsigned());
+ EXPECT_EQ(7, psid2.second.asUint16());
+}
+
} // anonymous namespace
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
Option optv6(Option::V6, 258);
EXPECT_TRUE(optv6.getEncapsulatedSpace().empty());
- optv6.setEncapsulatedSpace("dhcp6");
- EXPECT_EQ("dhcp6", optv6.getEncapsulatedSpace());
+ optv6.setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ EXPECT_EQ(DHCP6_OPTION_SPACE, optv6.getEncapsulatedSpace());
Option optv4(Option::V4, 125);
EXPECT_TRUE(optv4.getEncapsulatedSpace().empty());
- optv4.setEncapsulatedSpace("dhcp4");
- EXPECT_EQ("dhcp4", optv4.getEncapsulatedSpace());
-
+ optv4.setEncapsulatedSpace(DHCP4_OPTION_SPACE);
+ EXPECT_EQ(DHCP4_OPTION_SPACE, optv4.getEncapsulatedSpace());
}
// This test verifies that cloneInternal returns NULL pointer if
duid_(duid), hwaddr_(), host_identifiers_(), host_(),
fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
hostname_(hostname), callout_handle_(callout_handle),
- allocated_resources_(), ias_() {
+ allocated_resources_(), ias_(), pd_exclude_requested_(false) {
// Initialize host identifiers.
if (duid) {
// non-PD leases.
uint8_t prefix_len = 128;
if (ctx.currentIA().type_ == Lease::TYPE_PD) {
- Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
+ pool = boost::dynamic_pointer_cast<Pool6>(
ctx.subnet_->getPool(ctx.currentIA().type_, candidate, false));
- /// @todo: verify that the pool is non-null
- prefix_len = pool->getLength();
+ if (pool) {
+ prefix_len = pool->getLength();
+ }
}
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
/// @brief Container holding IA specific contexts.
std::vector<IAContext> ias_;
+ /// @brief Indicates if PD exclude option has been requested by a
+ /// client.
+ bool pd_exclude_requested_;
+
/// @brief Convenience method adding allocated prefix or address.
///
/// @param prefix Prefix or address.
// from the option spaces they encapsulate.
for (OptionContainer::const_iterator opt = options->begin();
opt != options->end(); ++opt) {
- // Get encapsulated option space for the option.
- const std::string& encap_space = opt->option_->getEncapsulatedSpace();
- // Empty value means that no option space is encapsulated.
- if (!encap_space.empty()) {
- // Retrieve all options from the encapsulated option space.
- OptionContainerPtr encap_options = getAll(encap_space);
- for (OptionContainer::const_iterator encap_opt =
- encap_options->begin(); encap_opt != encap_options->end();
- ++encap_opt) {
- // Add sub-option if there isn't one added already.
- if (!opt->option_->getOption(encap_opt->option_->getType())) {
- opt->option_->addOption(encap_opt->option_);
- }
+ encapsulateInternal(opt->option_);
+ }
+}
+
+void
+CfgOption::encapsulateInternal(const OptionPtr& option) {
+ // Get encapsulated option space for the option.
+ const std::string& encap_space = option->getEncapsulatedSpace();
+ // Empty value means that no option space is encapsulated.
+ if (!encap_space.empty()) {
+ // Retrieve all options from the encapsulated option space.
+ OptionContainerPtr encap_options = getAll(encap_space);
+ for (OptionContainer::const_iterator encap_opt =
+ encap_options->begin(); encap_opt != encap_options->end();
+ ++encap_opt) {
+ // Add sub-option if there isn't one added already.
+ if (!option->getOption(encap_opt->option_->getType())) {
+ option->addOption(encap_opt->option_);
+ }
+ // This is a workaround for preventing infinite recursion when
+ // trying to encapsulate options created with default global option
+ // spaces.
+ if (encap_space != DHCP4_OPTION_SPACE &&
+ encap_space != DHCP6_OPTION_SPACE) {
+ encapsulateInternal(encap_opt->option_);
}
}
}
/// which encapsulated options are appended.
void encapsulateInternal(const std::string& option_space);
+ /// @brief Appends encapsulated options from the option space encapsulated
+ /// by the specified option.
+ ///
+ /// This method will go over all options belonging to the encapsulated space
+ /// and will check which option spaces they encapsulate recursively,
+ /// adding these options to the current option
+ ///
+ /// @param option which encapsulated options.
+ void encapsulateInternal(const OptionPtr& option);
+
/// @brief Merges data from two option containers.
///
/// This method merges options from one option container to another
" space '" << option_space << "'");
// Must not override standard option definition.
- } else if (((option_space == DHCP4_OPTION_SPACE) &&
- LibDHCP::isStandardOption(Option::V4, def->getCode()) &&
- LibDHCP::getOptionDef(Option::V4, def->getCode())) ||
- ((option_space == DHCP6_OPTION_SPACE) &&
- LibDHCP::isStandardOption(Option::V6, def->getCode()) &&
- LibDHCP::getOptionDef(Option::V6, def->getCode()))) {
+ } else if (LibDHCP::getOptionDef(option_space, def->getCode())) {
isc_throw(BadValue, "unable to override definition of option '"
<< def->getCode() << "' in standard option space '"
<< option_space << "'");
// class, using option definition. Thus, we need to find the
// option definition for this option code and option space.
- // If the option space is a standard DHCPv4 or DHCPv6 option space,
- // this is most likely a standard option, for which we have a
- // definition created within libdhcp++.
- OptionDefinitionPtr def;
- if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
- def = LibDHCP::getOptionDef(universe_, code_);
- }
+ // Check if this is a standard option.
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
// Otherwise, we may check if this an option encapsulated within the
// vendor space.
std::string
OptionDataParser::extractSpace() const {
- std::string space = address_family_ == AF_INET ? "dhcp4" : "dhcp6";
+ std::string space = address_family_ == AF_INET ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
try {
space = string_values_->getParam("space");
OptionDefinitionPtr
OptionDataParser::findOptionDefinition(const std::string& option_space,
const SearchKey& search_key) const {
- const Option::Universe u = address_family_ == AF_INET ?
- Option::V4 : Option::V6;
- OptionDefinitionPtr def;
-
- if ((option_space == DHCP4_OPTION_SPACE) ||
- (option_space == DHCP6_OPTION_SPACE)) {
- def = LibDHCP::getOptionDef(u, search_key);
-
- }
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(option_space, search_key);
if (!def) {
// Check if this is a vendor-option. If it is, get vendor-specific
// definition.
uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
if (vendor_id) {
+ const Option::Universe u = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
}
}
std::string record_types =
string_values_->getOptionalParam("record-types", "");
std::string space = string_values_->getOptionalParam("space",
- global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
+ global_context_->universe_ == Option::V4 ? DHCP4_OPTION_SPACE :
+ DHCP6_OPTION_SPACE);
std::string encapsulates =
string_values_->getOptionalParam("encapsulate", "");
Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
- :Pool(type, first, last), prefix_len_(128) {
+ : Pool(type, first, last), prefix_len_(128),
+ excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+ excluded_prefix_len_(0) {
// check if specified address boundaries are sane
if (!first.isV6() || !last.isV6()) {
}
Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
- uint8_t prefix_len, uint8_t delegated_len /* = 128 */)
- :Pool(type, prefix, IOAddress("::")), prefix_len_(delegated_len) {
+ const uint8_t prefix_len, const uint8_t delegated_len /* = 128 */)
+ : Pool(type, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+ prefix_len_(delegated_len),
+ excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+ excluded_prefix_len_(0) {
+
+ init(type, prefix, prefix_len, delegated_len,
+ IOAddress::IPV6_ZERO_ADDRESS(), 0);
+}
- // check if the prefix is sane
+Pool6::Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len)
+ : Pool(Lease::TYPE_PD, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+ prefix_len_(delegated_len),
+ excluded_prefix_(excluded_prefix),
+ excluded_prefix_len_(excluded_prefix_len) {
+
+ init(Lease::TYPE_PD, prefix, prefix_len, delegated_len, excluded_prefix,
+ excluded_prefix_len);
+
+ // The excluded prefix can only be specified using this constructor.
+ // Therefore, the initialization of the excluded prefix is takes place
+ // here, rather than in the init(...) function.
+ if (!excluded_prefix_.isV6()) {
+ isc_throw(BadValue, "excluded prefix must be an IPv6 prefix");
+ }
+
+ // An "unspecified" prefix should have both value and length equal to 0.
+ if ((excluded_prefix_.isV6Zero() && (excluded_prefix_len_ != 0)) ||
+ (!excluded_prefix_.isV6Zero() && (excluded_prefix_len_ == 0))) {
+ isc_throw(BadValue, "invalid excluded prefix "
+ << excluded_prefix_ << "/"
+ << static_cast<unsigned>(excluded_prefix_len_));
+ }
+
+ // If excluded prefix has been specified.
+ if (!excluded_prefix_.isV6Zero() && (excluded_prefix_len_ != 0)) {
+
+ // Excluded prefix length must not be greater than 128.
+ if (excluded_prefix_len_ > 128) {
+ isc_throw(BadValue, "excluded prefix length "
+ << static_cast<unsigned>(excluded_prefix_len_)
+ << " must not be greater than 128");
+ }
+
+ // Excluded prefix must be a sub-prefix of a delegated prefix. First
+ // check the prefix length as it is less involved.
+ if (excluded_prefix_len_ <= prefix_len_) {
+ isc_throw(BadValue, "excluded prefix length "
+ << static_cast<unsigned>(excluded_prefix_len_)
+ << " must be lower than the delegated prefix length "
+ << static_cast<unsigned>(prefix_len_));
+ }
+
+ /// @todo Check that the prefixes actually match. Theoretically, a
+ /// user could specify a prefix which sets insgnificant bits. We should
+ /// clear insignificant bits based on the prefix length but this
+ /// should be considered a part of the IOAddress class, perhaps and
+ /// requires a bit of work (mainly in terms of testing).
+ }
+}
+
+void
+Pool6::init(const Lease::Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& /*excluded_prefix*/,
+ const uint8_t excluded_prefix_len) {
+ // Check if the prefix is sane
if (!prefix.isV6()) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
- // check if the prefix length is sane
+ // Check if the prefix length is sane
if (prefix_len == 0 || prefix_len > 128) {
- isc_throw(BadValue, "Invalid prefix length: " << static_cast<unsigned>(prefix_len));
+ isc_throw(BadValue, "Invalid prefix length: "
+ << static_cast<unsigned>(prefix_len));
}
if (prefix_len > delegated_len) {
- isc_throw(BadValue, "Delegated length (" << static_cast<int>(delegated_len)
+ isc_throw(BadValue, "Delegated length ("
+ << static_cast<int>(delegated_len)
<< ") must be longer than or equal to prefix length ("
<< static_cast<int>(prefix_len) << ")");
}
<< " be 128.");
}
+ // excluded_prefix_len == 0 means there's no excluded prefix at all.
+ if (excluded_prefix_len && (excluded_prefix_len < delegated_len)) {
+ isc_throw(BadValue, "Excluded prefix (" << static_cast<int>(excluded_prefix_len)
+ << ") must be longer than the delegated prefix length ("
+ << static_cast<int>(delegated_len));
+ }
+
/// @todo: We should probably implement checks against weird addresses
/// here, like ::, starting with fe80, starting with ff etc. .
std::string
Pool6::toText() const {
- std::stringstream tmp;
- tmp << "type=" << Lease::typeToText(type_) << ", " << first_
- << "-" << last_ << ", delegated_len="
- << static_cast<int>(prefix_len_);
- return (tmp.str());
+ std::ostringstream s;
+ s << "type=" << Lease::typeToText(type_) << ", " << first_
+ << "-" << last_ << ", delegated_len="
+ << static_cast<int>(prefix_len_);
+
+ if (excluded_prefix_len_ > 0) {
+ s << ", excluded_prefix=" << excluded_prefix_
+ << ", excluded_prefix_len="
+ << static_cast<unsigned>(excluded_prefix_len_);
+ }
+ return (s.str());
}
}; // end of isc::dhcp namespace
/// @param type type of the pool (IA, TA or PD)
/// @param prefix specifies prefix of the pool
/// @param prefix_len specifies prefix length of the pool
- /// @param delegated_len specifies lenght of the delegated prefixes
+ /// @param delegated_len specifies length of the delegated prefixes
Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len, uint8_t delegated_len = 128);
+ /// @brief Constructor for DHCPv6 prefix pool with an excluded prefix.
+ ///
+ /// If @c excluded_prefix is equal to '::' and the @c excluded_prefix_len
+ /// is equal to 0, the excluded prefix is assumed to be unspecified for
+ /// the pool. In this case, the server will not send the Prefix Exclude
+ /// option to a client.
+ ///
+ /// @param prefix specified a prefix of the pool.
+ /// @param prefix_Leb specifies prefix length of the pool.
+ /// @param delegated_len specifies length of the delegated prefixes.
+ /// @param excluded_prefix specifies an excluded prefix as per RFC6603.
+ /// @param excluded_prefix_len specifies length of an excluded prefix.
+ Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
/// @brief returns pool type
///
/// @return pool type
return (prefix_len_);
}
+ /// @brief Returns an excluded prefix.
+ ///
+ /// An excluded prefix can be specified for a prefix pool as specified
+ /// in RFC6603.
+ ///
+ /// @return Reference to an IOAddress object representing an excluded
+ /// prefix pool. A value of '::' if the excluded prefix is not specified.
+ const isc::asiolink::IOAddress& getExcludedPrefix() const{
+ return (excluded_prefix_);
+ }
+
+ /// @brief Returns an excluded prefix length.
+ ///
+ /// An excluded prefix can be specified for a prefix pool as specified
+ /// in RFC6603.
+ ///
+ /// @return Excluded prefix length in the range of 2 to 128, if the
+ /// excluded prefix is specified. A value of 0 if the excluded prefix
+ /// is not specified.
+ uint8_t getExcludedPrefixLength() const{
+ return (excluded_prefix_len_);
+ }
+
/// @brief returns textual representation of the pool
///
/// @return textual representation
virtual std::string toText() const;
private:
+
+ /// @brief Generic method initializing a DHCPv6 pool.
+ ///
+ /// This method should be called by the constructors to initialize
+ /// DHCPv6 pools.
+ ///
+ /// @param Lease/pool type.
+ /// @param prefix An address or delegated prefix (depending on the
+ /// pool type specified as @c type).
+ /// @param prefix_len Prefix length. If a pool is an address pool,
+ /// this value should be set to 128.
+ /// @param delegated_len Length of the delegated prefixes. If a pool
+ /// is an address pool, this value should be set to 128.
+ /// @param excluded_prefix An excluded prefix as per RFC6603. This
+ /// value should only be specified for prefix pools. The value of
+ /// '::' means "unspecified".
+ /// @param excluded_prefix_len Length of the excluded prefix. This
+ /// is only specified for prefix pools. The value of 0 should be
+ /// used when @c excluded_prefix is not specified.
+ void init(const Lease::Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
/// @brief Defines prefix length (for TYPE_PD only)
uint8_t prefix_len_;
+
+ /// @brief The excluded prefix (RFC6603).
+ ///
+ /// This prefix can only be specified for DHCPv6 prefix pools.
+ /// An "unspecified" prefix has a value of '::'.
+ isc::asiolink::IOAddress excluded_prefix_;
+
+ /// @brief The excluded prefix length (RFC6603).
+ ///
+ /// This value can only be specified for DHCPv6 prefix pool.
+ /// An "unspecified" prefix has a length of 0.
+ uint8_t excluded_prefix_len_;
};
/// @brief a pointer an IPv6 Pool
// Check that an option definition can be added to the standard
// (dhcp4 and dhcp6) option spaces when the option code is not
// reserved by the standard option.
- OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+ OptionDefinitionPtr def6(new OptionDefinition("option-foo", 1000, "uint16"));
EXPECT_NO_THROW(cfg.add(def6, DHCP6_OPTION_SPACE));
OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
#include <dhcp/option_int.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option.h>
+#include <boost/foreach.hpp>
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
#include <iterator>
namespace {
+/// This class fixture for testing @c CfgOption class, holding option
+/// configuration.
+class CfgOptionTest : public ::testing::Test {
+public:
+
+ /// @brief Generates encapsulated options and adds them to CfgOption
+ ///
+ /// This method generates the following options:
+ /// - 1000-1019 options: uint16 with value 1234, encapsulate "foo"
+ /// - 1-19 options: uint8 with value 1, encapsulate "foo-subs"
+ /// - 1-9 options: uint8 with value 3
+ /// - 1020-1039 options: uint16 with value 2345, encapsulate "bar"
+ /// - 100-119 options: uint8 with value 2, encapsulate "bar-subs"
+ /// - 501-509 options: uint8 with value 4
+ void generateEncapsulatedOptions(CfgOption& cfg) {
+ // Create top-level options encapsulating "foo" option space.
+ for (uint16_t code = 1000; code < 1020; ++code) {
+ OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+ code, 1234));
+ option->setEncapsulatedSpace("foo");
+ ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Create top level options encapsulating "bar" option space.
+ for (uint16_t code = 1020; code < 1040; ++code) {
+ OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+ code, 2345));
+ option->setEncapsulatedSpace("bar");
+ ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Create sub-options belonging to "foo" option space and encapsulating
+ // foo-subs option space.
+ for (uint16_t code = 1; code < 20; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+ 0x01));
+ option->setEncapsulatedSpace("foo-subs");
+ ASSERT_NO_THROW(cfg.add(option, false, "foo"));
+ }
+
+ // Create sub-options belonging to "bar" option space and encapsulating
+ // bar-subs option space.
+ for (uint16_t code = 100; code < 119; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+ code, 0x02));
+ option->setEncapsulatedSpace("bar-subs");
+ ASSERT_NO_THROW(cfg.add(option, false, "bar"));
+ }
+
+ // Create sub-options belonging to "foo-subs" option space.
+ for (uint16_t code = 1; code < 10; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+ 0x03));
+ ASSERT_NO_THROW(cfg.add(option, false, "foo-subs"));
+ }
+
+ // Create sub-options belonging to "bar-subs" option space.
+ for (uint16_t code = 501; code < 510; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+ code, 0x04));
+ ASSERT_NO_THROW(cfg.add(option, false, "bar-subs"));
+ }
+ }
+};
+
// This test verifies the empty predicate.
-TEST(CfgOptionTest, empty) {
+TEST_F(CfgOptionTest, empty) {
CfgOption cfg1;
CfgOption cfg2;
// Add an option to each configuration
OptionPtr option(new Option(Option::V6, 1));
- ASSERT_NO_THROW(cfg1.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg1.add(option, false, DHCP6_OPTION_SPACE));
ASSERT_NO_THROW(cfg2.add(option, true, "isc"));
// The first option configuration has an option
}
// This test verifies that the option configurations can be compared.
-TEST(CfgOptionTest, equals) {
+TEST_F(CfgOptionTest, equals) {
CfgOption cfg1;
CfgOption cfg2;
// This test verifies that multiple options can be added to the configuration
// and that they can be retrieved using the option space name.
-TEST(CfgOptionTest, add) {
+TEST_F(CfgOptionTest, add) {
CfgOption cfg;
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
}
// Add 7 options to another option space. The option codes partially overlap
}
// Get options from the Subnet and check if all 10 are there.
- OptionContainerPtr options = cfg.getAll("dhcp6");
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(10, options->size());
}
// This test verifies that two option configurations can be merged.
-TEST(CfgOptionTest, merge) {
+TEST_F(CfgOptionTest, merge) {
CfgOption cfg_src;
CfgOption cfg_dst;
// from the range of 100 to 109 and holding one byte of data equal to 0xFF.
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
- ASSERT_NO_THROW(cfg_src.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg_src.add(option, false, DHCP6_OPTION_SPACE));
}
// Create collection of options in vendor space 123, with option codes
// 100 to 108.
for (uint16_t code = 100; code < 110; code += 2) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
- ASSERT_NO_THROW(cfg_dst.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg_dst.add(option, false, DHCP6_OPTION_SPACE));
}
// Create collection of options having odd option codes in the range of
// Validate the options in the dhcp6 option space in the destination.
for (uint16_t code = 100; code < 110; ++code) {
- OptionDescriptor desc = cfg_dst.get("dhcp6", code);
+ OptionDescriptor desc = cfg_dst.get(DHCP6_OPTION_SPACE, code);
ASSERT_TRUE(desc.option_);
ASSERT_EQ(1, desc.option_->getData().size());
// The options with even option codes should hold one byte of data
// This test verifies that the options configuration can be copied between
// objects.
-TEST(CfgOptionTest, copy) {
+TEST_F(CfgOptionTest, copy) {
CfgOption cfg_src;
// Add 10 options to the custom option space in the source configuration.
for (uint16_t code = 100; code < 110; ++code) {
// This test verifies that encapsulated options are added as sub-options
// to the top level options on request.
-TEST(CfgOptionTest, encapsulate) {
+TEST_F(CfgOptionTest, encapsulate) {
CfgOption cfg;
- // Create top-level options encapsulating "foo" option space.
- for (uint16_t code = 1000; code < 1020; ++code) {
- OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
- code, 1234));
- option->setEncapsulatedSpace("foo");
- ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
- }
-
- // Create top level options encapsulating "bar" option space.
- for (uint16_t code = 1020; code < 1040; ++code) {
- OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
- code, 2345));
- option->setEncapsulatedSpace("bar");
- ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
- }
-
- // Create sub-options belonging to "foo" option space.
- for (uint16_t code = 1; code < 20; ++code) {
- OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
- 0x01));
- ASSERT_NO_THROW(cfg.add(option, false, "foo"));
- }
- // Create sub-options belonging to "bar" option space.
- for (uint16_t code = 100; code < 130; ++code) {
- OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
- code, 0x02));
- ASSERT_NO_THROW(cfg.add(option, false, "bar"));
- }
+ generateEncapsulatedOptions(cfg);
- // Append options from "foo" and "bar" space as sub-options.
+ // Append options from "foo" and "bar" space as sub-options and options
+ // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar"
+ // options.
ASSERT_NO_THROW(cfg.encapsulate());
// Verify that we have 40 top-level options.
OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(40, options->size());
+ // Iterate over top level options.
for (uint16_t code = 1000; code < 1040; ++code) {
+
OptionUint16Ptr option = boost::dynamic_pointer_cast<
OptionUint16>(cfg.get(DHCP6_OPTION_SPACE, code).option_);
ASSERT_TRUE(option) << "option with code " << code << " not found";
- const OptionCollection& suboptions = option->getOptions();
- for (OptionCollection::const_iterator suboption =
- suboptions.begin(); suboption != suboptions.end();
- ++suboption) {
- OptionUint8Ptr opt = boost::dynamic_pointer_cast<
- OptionUint8>(suboption->second);
- ASSERT_TRUE(opt);
- if (code < 1020) {
- EXPECT_EQ(0x01, opt->getValue());
+
+ // First level sub options. There are 19 sub-options for each top
+ // level option.
+ const OptionCollection& first_level = option->getOptions();
+ ASSERT_EQ(19, first_level.size());
+
+ // Iterate over all first level sub-options.
+ std::pair<unsigned int, OptionPtr> first_level_opt;
+ BOOST_FOREACH(first_level_opt, first_level) {
+ // Each option in this test comprises a single one byte field and
+ // should cast to OptionUint8 type.
+ OptionUint8Ptr first_level_uint8 = boost::dynamic_pointer_cast<
+ OptionUint8>(first_level_opt.second);
+ ASSERT_TRUE(first_level_uint8);
+
+ const unsigned int value = static_cast<unsigned int>(first_level_uint8->getValue());
+ // There are two sets of first level sub-options. Those that include
+ // a value of 1 and those that include a value of 2.
+ if (first_level_uint8->getType() < 20) {
+ EXPECT_EQ(1, value);
} else {
- EXPECT_EQ(0x02, opt->getValue());
+ EXPECT_EQ(2, value);
+ }
+
+ // Each first level sub-option should include 9 second level
+ // sub options.
+ const OptionCollection& second_level = first_level_uint8->getOptions();
+ ASSERT_EQ(9, second_level.size());
+
+ // Iterate over sub-options and make sure they include the expected
+ // values.
+ std::pair<unsigned int, OptionPtr> second_level_opt;
+ BOOST_FOREACH(second_level_opt, second_level) {
+ OptionUint8Ptr second_level_uint8 = boost::dynamic_pointer_cast<
+ OptionUint8>(second_level_opt.second);
+ ASSERT_TRUE(second_level_uint8);
+ const unsigned value = static_cast<
+ unsigned>(second_level_uint8->getValue());
+ // Certain sub-options should have a value of 3, other the values
+ // of 4.
+ if (second_level_uint8->getType() < 20) {
+ EXPECT_EQ(3, value);
+ } else {
+ EXPECT_EQ(4, value);
+ }
}
}
}
// This test verifies that single option can be retrieved from the configuration
// using option code and option space.
-TEST(CfgOptionTest, get) {
+TEST_F(CfgOptionTest, get) {
CfgOption cfg;
// Add 10 options to a "dhcp6" option space in the subnet.
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
}
// Check that we can get each added option descriptor using
// Returned descriptor should contain NULL option ptr.
EXPECT_FALSE(desc.option_);
// Now, try the valid option space.
- desc = cfg.get("dhcp6", code);
+ desc = cfg.get(DHCP6_OPTION_SPACE, code);
// Test that the option code matches the expected code.
ASSERT_TRUE(desc.option_);
EXPECT_EQ(code, desc.option_->getType());
// This test verifies that the same options can be added to the configuration
// under different option space.
-TEST(CfgOptionTest, addNonUniqueOptions) {
+TEST_F(CfgOptionTest, addNonUniqueOptions) {
CfgOption cfg;
// Create a set of options with non-unique codes.
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(cfg.add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(cfg.add(option, false, DHCP6_OPTION_SPACE));
}
}
// Sanity check that all options are there.
- OptionContainerPtr options = cfg.getAll("dhcp6");
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(20, options->size());
// Use container index #1 to get the options by their codes.
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
- ASSERT_NO_THROW(cfg.add(option, persistent, "dhcp6"));
+ ASSERT_NO_THROW(cfg.add(option, persistent, DHCP6_OPTION_SPACE));
}
// Get added options from the subnet.
- OptionContainerPtr options = cfg.getAll("dhcp6");
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
// options->get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
}
// This test verifies that the vendor option can be added to the configuration.
-TEST(CfgOptionTest, addVendorOptions) {
+TEST_F(CfgOptionTest, addVendorOptions) {
CfgOption cfg;
// Differentiate options by their codes (100-109)
// This test verifies that option space names for the vendor options are
// correct.
-TEST(CfgOptionTest, getVendorIdsSpaceNames) {
+TEST_F(CfgOptionTest, getVendorIdsSpaceNames) {
CfgOption cfg;
// Create 10 options, each goes under a different vendor id.
cfg_option = cclass->getCfgOption();
ASSERT_TRUE(cfg_option);
OptionContainerPtr oc;
- ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
EXPECT_EQ(0, oc->size());
// Verify we have no expression.
cfg_option = cclass->getCfgOption();
ASSERT_TRUE(cfg_option);
OptionContainerPtr oc;
- ASSERT_TRUE(oc = cclass->getCfgOption()->getAll("dhcp4"));
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
EXPECT_EQ(0, oc->size());
// Verify we can retrieve the expression
EXPECT_EQ("MICROSOFT", cclass->getName());
// Our one option should exist.
- OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
ASSERT_TRUE(od.option_);
EXPECT_EQ(6, od.option_->getType());
EXPECT_EQ("MICROSOFT", cclass->getName());
// Our one option should exist.
- OptionDescriptor od = cclass->getCfgOption()->get("dhcp4", 6);
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
ASSERT_TRUE(od.option_);
EXPECT_EQ(6, od.option_->getType());
#include <config.h>
#include <dhcpsrv/client_class_def.h>
+#include <dhcp/option_space.h>
#include <exceptions/exceptions.h>
#include <boost/scoped_ptr.hpp>
#include <asiolink/io_address.h>
OptionPtr option;
test_options.reset(new CfgOption());
option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
option.reset(new Option(Option::V6, 101, OptionBuffer(10, 0xFF)));
ASSERT_NO_THROW(test_options->add(option, false, "isc"));
option.reset(new Option(Option::V6, 100, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(test_options->add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP6_OPTION_SPACE));
// Now remake the client class with cfg_option
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
ASSERT_TRUE(class_options);
// Now make sure we can find all the options
- OptionDescriptor opt_desc = class_options->get("dhcp4",17);
+ OptionDescriptor opt_desc = class_options->get(DHCP4_OPTION_SPACE,17);
ASSERT_TRUE(opt_desc.option_);
EXPECT_EQ(17, opt_desc.option_->getType());
ASSERT_TRUE(opt_desc.option_);
EXPECT_EQ(101, opt_desc.option_->getType());
- opt_desc = class_options->get("dhcp6",100);
+ opt_desc = class_options->get(DHCP6_OPTION_SPACE,100);
ASSERT_TRUE(opt_desc.option_);
EXPECT_EQ(100, opt_desc.option_->getType());
}
OptionPtr option;
test_options.reset(new CfgOption());
option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
// Now remake the client class with cfg_option
ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class_one", expr,
// Make a class that with same name and expression, but different options
// verify that the equality tools reflect that the classes are not equal.
option.reset(new Option(Option::V4, 20, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(test_options->add(option, false, "dhcp4"));
+ ASSERT_NO_THROW(test_options->add(option, false, DHCP4_OPTION_SPACE));
ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
test_options)));
EXPECT_FALSE(cclass->equals(*cclass2));
using namespace std;
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
// Verify that the option definition can be retrieved.
OptionDefinitionPtr def =
- CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("dhcp6", 10000);
+ CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 10000);
ASSERT_TRUE(def);
// Verify that the option definition is correct.
ASSERT_EQ(0, rcode);
// Verify that the option can be retrieved.
- OptionPtr opt = getOptionPtr("dhcp4", DHO_BOOT_FILE_NAME);
+ OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
ASSERT_TRUE(opt);
util::OutputBuffer buf(100);
// Verify that the option data is correct.
OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
- OptionCustom>(getOptionPtr("dhcp4", 16));
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
// Verify that the option data is correct.
addr_opt = boost::dynamic_pointer_cast<
- OptionCustom>(getOptionPtr("dhcp4", 16));
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
// Verify that the option data is correct.
addr_opt = boost::dynamic_pointer_cast<
- OptionCustom>(getOptionPtr("dhcp4", 16));
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
}
+// This test verifies that definitions of standard encapsulated
+// options can be used.
+TEST_F(ParseConfigTest, encapsulatedOptionData) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"space\": \"s46-cont-mape-options\","
+ " \"name\": \"s46-rule\","
+ " \"data\": \"1, 0, 24, 192.0.2.0, 2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ // Make sure that we're using correct universe.
+ parser_context_->universe_ = Option::V6;
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option data is correct.
+ OptionCustomPtr s46_rule = boost::dynamic_pointer_cast<OptionCustom>
+ (getOptionPtr(MAPE_V6_OPTION_SPACE, D6O_S46_RULE));
+ ASSERT_TRUE(s46_rule);
+
+ uint8_t flags;
+ uint8_t ea_len;
+ uint8_t prefix4_len;
+ IOAddress ipv4_prefix(IOAddress::IPV4_ZERO_ADDRESS());
+ PrefixTuple ipv6_prefix(PrefixLen(0), IOAddress::IPV6_ZERO_ADDRESS());;
+
+ ASSERT_NO_THROW({
+ flags = s46_rule->readInteger<uint8_t>(0);
+ ea_len = s46_rule->readInteger<uint8_t>(1);
+ prefix4_len = s46_rule->readInteger<uint8_t>(2);
+ ipv4_prefix = s46_rule->readAddress(3);
+ ipv6_prefix = s46_rule->readPrefix(4);
+ });
+
+ EXPECT_EQ(1, flags);
+ EXPECT_EQ(0, ea_len);
+ EXPECT_EQ(24, prefix4_len);
+ EXPECT_EQ("192.0.2.0", ipv4_prefix.toText());
+ EXPECT_EQ(64, ipv6_prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", ipv6_prefix.second.toText());
+}
+
// This test checks behavior of the configuration parser for option data
// for different values of csv-format parameter and when there is no
// option definition.
"}";
ASSERT_NO_THROW(rcode = parseConfiguration(config));
ASSERT_EQ(0, rcode);
- OptionPtr opt = getOptionPtr("dhcp6", 25000);
+ OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getData().size());
EXPECT_EQ(0, opt->getData()[0]);
"}";
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
- opt = getOptionPtr("dhcp6", 25000);
+ opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
ASSERT_TRUE(opt);
ASSERT_EQ(3, opt->getData().size());
EXPECT_EQ(0x12, opt->getData()[0]);
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
- Option6AddrLst>(getOptionPtr("dhcp6", 23));
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
- Option6AddrLst>(getOptionPtr("dhcp6", 23));
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
- Option6AddrLst>(getOptionPtr("dhcp6", 23));
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
rcode = 0;
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
- opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+ opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
- Option6AddrLst>(getOptionPtr("dhcp6", 2345));
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 2345));
ASSERT_TRUE(opt);
ASSERT_EQ(2, opt->getAddresses().size());
EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
rcode = 0;
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
- opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+ opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
2345));
ASSERT_TRUE(opt);
ASSERT_EQ(2, opt->getAddresses().size());
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
- Option6AddrLst>(getOptionPtr("dhcp6", D6O_DHCPV4_O_DHCPV6_SERVER));
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
ASSERT_TRUE(opt);
ASSERT_EQ(0, opt->getAddresses().size());
}
int rcode = 0;
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
- const OptionPtr opt = getOptionPtr("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ const OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
ASSERT_TRUE(opt);
ASSERT_EQ(0, opt->getOptions().size());
}
#include <dhcp/libdhcp++.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option6_addrlst.h>
-#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcp/option_int.h>
#include <dhcp/option_vendor.h>
OptionPtr
retrieveOption(const Host& host, const std::string& option_space,
const uint16_t option_code) const {
- if ((option_space != "dhcp6") && (option_space != "dhcp4")) {
+ if ((option_space != DHCP6_OPTION_SPACE) && (option_space != DHCP4_OPTION_SPACE)) {
return (OptionPtr());
}
// Retrieve a pointer to the appropriate container depending if we're
// interested in DHCPv4 or DHCPv6 options.
- ConstCfgOptionPtr cfg_option = (option_space == "dhcp4" ?
+ ConstCfgOptionPtr cfg_option = (option_space == DHCP4_OPTION_SPACE ?
host.getCfgOption4() : host.getCfgOption6());
// Retrieve options.
// Retrieve and sanity check name servers.
Option4AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
- Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_NAME_SERVERS));
+ Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_NAME_SERVERS));
ASSERT_TRUE(opt_dns);
Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
ASSERT_EQ(2, dns_addrs.size());
// Retrieve and sanity check log servers.
Option4AddrLstPtr opt_log = boost::dynamic_pointer_cast<
- Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_LOG_SERVERS));
+ Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_LOG_SERVERS));
ASSERT_TRUE(opt_log);
Option4AddrLst::AddressContainer log_addrs = opt_log->getAddresses();
ASSERT_EQ(1, log_addrs.size());
// Retrieve and sanity check default IP TTL.
OptionUint8Ptr opt_ttl = boost::dynamic_pointer_cast<
- OptionUint8>(retrieveOption(*hosts[0], "dhcp4", DHO_DEFAULT_IP_TTL));
+ OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
ASSERT_TRUE(opt_ttl);
EXPECT_EQ(64, opt_ttl->getValue());
}
// Retrieve and sanity check DNS servers option.
Option6AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
- Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NAME_SERVERS));
+ Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NAME_SERVERS));
ASSERT_TRUE(opt_dns);
Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
ASSERT_EQ(2, dns_addrs.size());
// Retrieve and sanity check NIS servers option.
Option6AddrLstPtr opt_nis = boost::dynamic_pointer_cast<
- Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NIS_SERVERS));
+ Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NIS_SERVERS));
ASSERT_TRUE(opt_nis);
Option6AddrLst::AddressContainer nis_addrs = opt_nis->getAddresses();
ASSERT_EQ(1, nis_addrs.size());
// Retrieve and sanity check preference option.
OptionUint8Ptr opt_prf = boost::dynamic_pointer_cast<
- OptionUint8>(retrieveOption(*hosts[0], "dhcp6", D6O_PREFERENCE));
+ OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
ASSERT_TRUE(opt_prf);
EXPECT_EQ(11, opt_prf->getValue());
}
#include <config.h>
#include <dhcpsrv/host.h>
+#include <dhcp/option_space.h>
#include <util/encode/hex.h>
#include <util/range_utilities.h>
#include <boost/scoped_ptr.hpp>
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, "dhcp4"));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, DHCP4_OPTION_SPACE));
}
// Add 7 options to another option space. The option codes partially overlap
}
// Get options from the Subnet and check if all 10 are there.
- OptionContainerPtr options = host.getCfgOption4()->getAll("dhcp4");
+ OptionContainerPtr options = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(10, options->size());
// It should be possible to retrieve DHCPv6 options but the container
// should be empty.
- OptionContainerPtr options6 = host.getCfgOption6()->getAll("dhcp6");
+ OptionContainerPtr options6 = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options6);
EXPECT_TRUE(options6->empty());
// Also make sure that for dhcp4 option space no DHCPv6 options are
// returned. This is to check that containers for DHCPv4 and DHCPv6
// options do not share information.
- options6 = host.getCfgOption6()->getAll("dhcp4");
+ options6 = host.getCfgOption6()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options6);
EXPECT_TRUE(options6->empty());
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, DHCP6_OPTION_SPACE));
}
// Add 7 options to another option space. The option codes partially overlap
}
// Get options from the Subnet and check if all 10 are there.
- OptionContainerPtr options = host.getCfgOption6()->getAll("dhcp6");
+ OptionContainerPtr options = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(10, options->size());
// It should be possible to retrieve DHCPv4 options but the container
// should be empty.
- OptionContainerPtr options4 = host.getCfgOption4()->getAll("dhcp4");
+ OptionContainerPtr options4 = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
ASSERT_TRUE(options4);
EXPECT_TRUE(options4->empty());
// Also make sure that for dhcp6 option space no DHCPv4 options are
// returned. This is to check that containers for DHCPv4 and DHCPv6
// options do not share information.
- options4 = host.getCfgOption4()->getAll("dhcp6");
+ options4 = host.getCfgOption4()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options4);
EXPECT_TRUE(options4->empty());
77, 77));
}
+// Checks that prefix pools with excluded prefixes are handled properly.
+TEST(Pool6Test, PDExclude) {
+ Pool6Ptr pool;
+
+ // Create a pool with a good excluded prefix. The good excluded prefix
+ // is the one for which is a sub-prefix of the main prefix.
+ ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1:2::"), 120)));
+
+ // Verify pool properties.
+ EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+ EXPECT_EQ(112, pool->getLength());
+ EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+ EXPECT_EQ("2001:db8:1:2::", pool->getExcludedPrefix().toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pool->getExcludedPrefixLength()));
+
+ // Create another pool instance, but with the excluded prefix being
+ // "unspecified".
+ ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress::IPV6_ZERO_ADDRESS(), 0)));
+
+ EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+ EXPECT_EQ(112, pool->getLength());
+ EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+ EXPECT_TRUE(pool->getExcludedPrefix().isV6Zero());
+ EXPECT_EQ(0, static_cast<unsigned>(pool->getExcludedPrefixLength()));
+
+ // Excluded prefix length must be greater than the main prefix length.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::"), 112),
+ BadValue);
+
+ // Again, the excluded prefix length must be greater than main prefix
+ // length.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::"), 104),
+ BadValue);
+
+ // The "unspecified" excluded prefix must have both values set to 0.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress("2001:db8:1::"), 0),
+ BadValue);
+
+ // Similar case as above, but the prefix value is 0 and the length
+ // is non zero.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress::IPV6_ZERO_ADDRESS(), 72),
+ BadValue);
+
+ // Excluded prefix must be an IPv6 prefix.
+ EXPECT_THROW(Pool6(IOAddress("10::"), 8, 16,
+ IOAddress("10.0.0.0"), 24),
+ BadValue);
+
+ // Excluded prefix length must not be greater than 128.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress("2001:db8:1::"), 129),
+ BadValue);
+}
+
// Checks that temporary address pools are handled properly
TEST(Pool6Test, TA) {
// Note: since we defined TA pool types during PD work, we can test it
}
// Simple check if toText returns reasonable values
-TEST(Poo6Test,toText) {
+TEST(Pool6Test,toText) {
Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
IOAddress("2001:db8::2"));
EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",
Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112",
pool2.toText());
+
+ Pool6 pool3(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::1000"), 120);
+ EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112,"
+ " excluded_prefix=2001:db8:1::1000, excluded_prefix_len=120",
+ pool3.toText());
+
}
// Checks if the number of possible leases in range is reported correctly.
// Add an option.
OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(10, 0xFF)));
- conf1.getCfgOption()->add(option, true, "dhcp6");
+ conf1.getCfgOption()->add(option, true, DHCP6_OPTION_SPACE);
// Add a class dictionary
conf1.setClientClassDictionary(ref_dictionary_);
#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->getCfgOption()->add(option2, false, "dhcp4"),
+ EXPECT_THROW(subnet->getCfgOption()->add(option2, false, DHCP4_OPTION_SPACE),
isc::BadValue);
}
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
}
// Add 7 options to another option space. The option codes partially overlap
}
// Get options from the Subnet and check if all 10 are there.
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_TRUE(options);
ASSERT_EQ(10, options->size());
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
}
}
// Sanity check that all options are there.
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
ASSERT_EQ(20, options->size());
// Use container index #1 to get the options by their codes.
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
- ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent, "dhcp6"));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent, DHCP6_OPTION_SPACE));
}
// Get added options from the subnet.
- OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
// options->get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
// Add 10 options to a "dhcp6" option space in the subnet.
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, "dhcp6"));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, DHCP6_OPTION_SPACE));
}
// Check that we can get each added option descriptor using
// Returned descriptor should contain NULL option ptr.
EXPECT_FALSE(desc.option_);
// Now, try the valid option space.
- desc = subnet->getCfgOption()->get("dhcp6", code);
+ desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, code);
// Test that the option code matches the expected code.
ASSERT_TRUE(desc.option_);
EXPECT_EQ(code, desc.option_->getType());
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/option_space.h>
#include <eval/eval_context.h>
#include <eval/parser.h>
#include <exceptions/exceptions.h>
EvalContext::convertOptionName(const std::string& option_name,
const isc::eval::location& loc)
{
- OptionDefinitionPtr option_def = LibDHCP::getOptionDef(option_universe_,
+ const std::string global_space = (option_universe_ == Option::V4) ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
+
+ OptionDefinitionPtr option_def = LibDHCP::getOptionDef(global_space,
option_name);
if (!option_def) {
- const std::string global_space =
- (option_universe_ == Option::V4) ? "dhcp4" : "dhcp6";
option_def = LibDHCP::getRuntimeOptionDef(global_space, option_name);
}