From 0c0ca2fde709f015a891c30620ce5f8828e998b0 Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Wed, 26 Oct 2016 08:18:05 +0200 Subject: [PATCH] [5016] Merged pull request #24 from github24 and squashed it. This change includes the code implemented by the pull request submitter as well as Marcin's fixes/changes on top of it, but without a review. --- AUTHORS | 6 + doc/Makefile.am | 1 + doc/examples/kea6/softwire46.json | 91 +++++ doc/guide/dhcp4-srv.xml | 4 +- doc/guide/dhcp6-srv.xml | 207 +++++++++- src/bin/dhcp4/dhcp4_srv.cc | 5 +- src/bin/dhcp4/tests/config_parser_unittest.cc | 50 +-- src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 8 +- src/bin/dhcp4/tests/dhcp4_test_utils.cc | 10 +- src/bin/dhcp6/dhcp6.spec | 12 + src/bin/dhcp6/dhcp6_srv.cc | 74 +++- src/bin/dhcp6/dhcp6_srv.h | 5 + src/bin/dhcp6/json_config_parser.cc | 23 +- src/bin/dhcp6/tests/config_parser_unittest.cc | 102 +++-- src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 2 +- src/bin/dhcp6/tests/renew_unittest.cc | 117 +++++- src/bin/dhcp6/tests/sarr_unittest.cc | 69 +++- src/lib/dhcp/Makefile.am | 1 + src/lib/dhcp/dhcp6.h | 20 +- src/lib/dhcp/docsis3_option_defs.h | 5 +- src/lib/dhcp/duid_factory.cc | 4 +- src/lib/dhcp/libdhcp++.cc | 105 +++-- src/lib/dhcp/libdhcp++.h | 39 +- src/lib/dhcp/option6_ia.cc | 7 +- src/lib/dhcp/option6_iaaddr.cc | 5 +- src/lib/dhcp/option6_iaprefix.cc | 5 +- src/lib/dhcp/option6_pdexclude.cc | 227 +++++++++++ src/lib/dhcp/option6_pdexclude.h | 134 +++++++ src/lib/dhcp/option_custom.cc | 144 ++++++- src/lib/dhcp/option_custom.h | 53 +++ src/lib/dhcp/option_data_types.cc | 213 +++++++++- src/lib/dhcp/option_data_types.h | 196 ++++++++- src/lib/dhcp/option_definition.cc | 83 +++- src/lib/dhcp/option_definition.h | 8 +- src/lib/dhcp/option_int.h | 5 +- src/lib/dhcp/option_space.h | 13 +- src/lib/dhcp/pkt4.cc | 2 +- src/lib/dhcp/pkt6.cc | 5 +- src/lib/dhcp/std_option_defs.h | 113 ++++-- src/lib/dhcp/tests/Makefile.am | 1 + src/lib/dhcp/tests/libdhcp++_unittest.cc | 139 +++---- .../dhcp/tests/option6_pdexclude_unittest.cc | 175 +++++++++ src/lib/dhcp/tests/option_custom_unittest.cc | 371 +++++++++++++++++- .../dhcp/tests/option_data_types_unittest.cc | 261 +++++++++++- .../dhcp/tests/option_definition_unittest.cc | 223 ++++++++++- src/lib/dhcp/tests/option_unittest.cc | 10 +- src/lib/dhcpsrv/alloc_engine.cc | 9 +- src/lib/dhcpsrv/alloc_engine.h | 4 + src/lib/dhcpsrv/cfg_option.cc | 38 +- src/lib/dhcpsrv/cfg_option.h | 10 + src/lib/dhcpsrv/cfg_option_def.cc | 7 +- src/lib/dhcpsrv/mysql_host_data_source.cc | 9 +- src/lib/dhcpsrv/parsers/dhcp_parsers.cc | 18 +- src/lib/dhcpsrv/pool.cc | 109 ++++- src/lib/dhcpsrv/pool.h | 80 +++- .../dhcpsrv/tests/cfg_option_def_unittest.cc | 2 +- src/lib/dhcpsrv/tests/cfg_option_unittest.cc | 199 +++++++--- .../tests/client_class_def_parser_unittest.cc | 8 +- .../tests/client_class_def_unittest.cc | 13 +- .../dhcpsrv/tests/dhcp_parsers_unittest.cc | 75 +++- .../generic_host_data_source_unittest.cc | 1 - .../tests/host_reservation_parser_unittest.cc | 16 +- src/lib/dhcpsrv/tests/host_unittest.cc | 17 +- src/lib/dhcpsrv/tests/pool_unittest.cc | 71 +++- src/lib/dhcpsrv/tests/srv_config_unittest.cc | 2 +- src/lib/dhcpsrv/tests/subnet_unittest.cc | 19 +- src/lib/eval/eval_context.cc | 8 +- 67 files changed, 3559 insertions(+), 509 deletions(-) create mode 100644 doc/examples/kea6/softwire46.json create mode 100644 src/lib/dhcp/option6_pdexclude.cc create mode 100644 src/lib/dhcp/option6_pdexclude.h create mode 100644 src/lib/dhcp/tests/option6_pdexclude_unittest.cc diff --git a/AUTHORS b/AUTHORS index 4d957b74e1..b15bb3554e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -117,6 +117,12 @@ We have received the following contributions: 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 diff --git a/doc/Makefile.am b/doc/Makefile.am index 3997e5cae5..5aa8fe72ed 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -29,6 +29,7 @@ nobase_dist_doc_DATA += examples/kea6/pgsql-reservations.json 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: diff --git a/doc/examples/kea6/softwire46.json b/doc/examples/kea6/softwire46.json new file mode 100644 index 0000000000..07b4bd7ac4 --- /dev/null +++ b/doc/examples/kea6/softwire46.json @@ -0,0 +1,91 @@ +# 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" + } + ] +} + +} + diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml index 2083709cf9..ffe0ef2e78 100644 --- a/doc/guide/dhcp4-srv.xml +++ b/doc/guide/dhcp4-srv.xml @@ -469,7 +469,7 @@ If a timeout is given though, it should be an integer greater than zero. -
+
Hosts Storage Kea is also able to store information about host reservations in the database. The hosts database configuration uses the same syntax as the lease @@ -1216,6 +1216,8 @@ It is merely echoed by the server fqdnFully qualified domain name (e.g. www.example.com) ipv4-addressIPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1) ipv6-addressIPv6 address in the usual colon notation (e.g. 2001:db8::1) + ipv6-prefixIPv6 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 + psidPSID 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 RFC 7597 for the details about the PSID wire representation recordStructured data that may comprise any types (except "record" and "empty") stringAny text uint88 bit unsigned integer with allowed values 0 to 255 diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index bf8a6ebe58..5703747b9e 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -799,7 +799,6 @@ temporarily override a list of interface names and listen on all interfaces.
- Subnet and Prefix Delegation Pools Subnets may also be configured to delegate prefixes, as defined in @@ -833,6 +832,42 @@ temporarily override a list of interface names and listen on all interfaces. ... } + +
+ +
+ Prefix Exclude Option + + For each delegated prefix the delegating router may choose to exclude + a single prefix out of the delegated prefix as specified in the + RFC 6603. + 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. + + +"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 + } + ] + } + ] +} +
@@ -1110,11 +1145,21 @@ temporarily override a list of interface names and listen on all interfaces. bootfile-param60binaryfalse client-arch-type61uint16true nii62record (uint8, uint8, uint8)false +aftr-name64fqdnfalse erp-local-domain-name65fqdnfalse rsoo66emptyfalse +pd-exclude67binaryfalse client-linklayer-addr79binaryfalse dhcp4o6-server-addr88ipv6-addresstrue +s46-rule89record (uint8, uint8, uint8, ipv4-address, ipv6-prefix)false +s46-br90ipv6-addressfalse +s46-dmr91ipv6-prefixfalse +s46-v4v6bind92record (ipv4-address, ipv6-prefix)false +s46-portparams93record(uint8, psid)false +s46-cont-mape94emptyfalse +s46-cont-mapt95emptyfalse +s46-cont-lw96emptyfalse @@ -1142,6 +1187,149 @@ temporarily override a list of interface names and listen on all interfaces.
+
+ Common Softwire46 Options + + Softwire46 options are involved in IPv4 over IPv6 provisioning by + means of tunneling or translation as specified in the + RFC 7598. + The following sections provide configuration examples of these + options. + + +
+ Softwire46 Container Options + + 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. + + + + 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: + +"Dhcp6": { + ... + "option-data": [ + { + "name": "s46-cont-mape" + } ], + ... +} + + + 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. + + + + 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. + +
+ +
+ S46 Rule Option + + The S46 Rule option is used for conveying the Basic Mapping Rule (BMR) + and Forwarding Mapping Rule (FMR). + +{ + "space": "s46-cont-mape-options", + "name": "s46-rule", + "data": "1, 0, 24, 192.0.2.0, 2001:db8:1::/64" +} + + Other possible "space" value is "s46-cont-mapt-options". + + +
+
+ S46 BR Option + + 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. + +{ + "space": "s46-cont-mape-options", + "name": "s46-br", + "data": "2001:db8:cafe::1", +} + + Other possible "space" value is "s46-cont-lw-options". + +
+ +
+ S46 DMR Option + + 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. + +{ + "space": "s46-cont-mapt-options", + "name": "s46-dmr", + "data": "2001:db8:cafe::/64", +} + + This option must not be included in other containers. + +
+ +
+ S46 IPv4/IPv6 Address Binding option. + + 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. + +{ + "space": "s46-cont-lw", + "name": "s46-v4v6bind", + "data": "192.0.2.3, 2001:db8:1:cafe::/64" +} + + This option must not be included in other containers. + +
+
+ S46 Port Parameters + + The S46 Port Parameters option specifies optional port set + information that MAY be provided to CEs + +{ + "space": "s46-rule-options", + "name": "s46-portparams", + "data": "2, 3/4", +} + + Other possible "space" value is "s46-v4v6bind" to include + this option in the S46 IPv4/IPv6 Address Binding option. + + + 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. + +
+
+
Custom DHCPv6 Options It is possible to define options in addition to the standard ones. @@ -2371,8 +2559,8 @@ should include options from the isc option space: "pd-pools": [ { "prefix": "2001:db8:1:8000::", - "prefix-len": 56, - "delegated-len": 64 + "prefix-len": 48, + "delegated-len": 64, } ], "reservations": [ @@ -3895,6 +4083,12 @@ If not specified, the default value is: to echo back the options, checks whether an option is RSOO-enabled, ability to mark additional options as RSOO-enabled. + + Prefix Exclude Option for DHCPv6-based Prefix + Delegation, + RFC + 6603: Prefix Exclude option is supported. + Client Link-Layer Address Option in DHCPv6, @@ -3909,6 +4103,13 @@ If not specified, the default value is: 7550: All recommendations related to the DHCPv6 server operation are supported. + + DHCPv6 Options for Configuration of Softwire + Address and Port-Mapped Clients, + RFC + 7598: All options specified in this specification are + supported by the DHCPv6 server. +
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 523b81a003..34727ddcf9 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -1209,7 +1209,7 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) { // 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_); @@ -1320,7 +1320,8 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) { // 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; diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 4f69f25aa4..286681486c 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -146,7 +146,7 @@ public: std::map 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"; @@ -158,19 +158,19 @@ public: 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; @@ -251,7 +251,7 @@ public: << "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 " @@ -475,7 +475,7 @@ public: template ReturnType retrieveOption(const Host& host, const uint16_t option_code) const { - return (retrieveOption(host, "dhcp4", option_code)); + return (retrieveOption(host, DHCP4_OPTION_SPACE, option_code)); } /// @brief Retrieve an option associated with a host. @@ -1823,7 +1823,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) { 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. @@ -1834,7 +1834,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) { // 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. @@ -1885,7 +1885,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) { checkResult(status, 0); def = CfgMgr::instance().getStagingCfg()-> - getCfgOptionDef()->get("dhcp4", 213); + getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 213); ASSERT_TRUE(def); // Check the option data. @@ -1927,10 +1927,10 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsGlobal) { 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. @@ -1994,13 +1994,13 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsSubnet) { // 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. @@ -2078,7 +2078,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) { // 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. @@ -2207,13 +2207,13 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { // 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()); @@ -2267,7 +2267,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) { 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. @@ -2303,7 +2303,7 @@ TEST_F(Dhcp4ParserTest, optionDataBoolean) { // Create configuration. Use standard option 19 (ip-forwarding). std::map params; params["name"] = "ip-forwarding"; - params["space"] = "dhcp4"; + params["space"] = DHCP4_OPTION_SPACE; params["code"] = "19"; params["data"] = "true"; params["csv-format"] = "true"; @@ -2414,7 +2414,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { 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. @@ -2439,7 +2439,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { 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>(); @@ -2517,7 +2517,7 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) { 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. @@ -2544,7 +2544,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) { ConstElementPtr x; std::map 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. @@ -2561,7 +2561,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) { 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()); @@ -2746,13 +2746,13 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) { // 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()); diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index af3c8667b0..0f7033c011 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -402,7 +402,7 @@ TEST_F(Dhcpv4SrvTest, initResponse) { // 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)); @@ -2170,7 +2170,7 @@ TEST_F(Dhcpv4SrvTest, relayLinkSelect) { 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)); @@ -2197,7 +2197,7 @@ TEST_F(Dhcpv4SrvTest, relayLinkSelect) { 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)); @@ -2280,7 +2280,7 @@ TEST_F(Dhcpv4SrvTest, subnetSelect) { 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)); diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc index cf6e61d36f..a2e38a9160 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc @@ -62,7 +62,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest() // 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_); @@ -110,24 +110,24 @@ void Dhcpv4SrvTest::configureRequestedOptions() { 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) { diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec index 6800fc25c4..1bc461610d 100644 --- a/src/bin/dhcp6/dhcp6.spec +++ b/src/bin/dhcp6/dhcp6.spec @@ -560,6 +560,18 @@ "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", diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 31cdcfab2b..ede67d9d2b 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ #include #include +#include #include #include #include @@ -873,6 +875,7 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question, void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx, const CfgOptionList& co_list) { // Client requests some options using ORO option. Try to @@ -881,18 +884,39 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, boost::dynamic_pointer_cast > (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& 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_); @@ -1569,6 +1593,20 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer, (*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, @@ -1839,10 +1877,28 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query, // 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()) @@ -2337,7 +2393,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { 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 @@ -2368,7 +2424,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { 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); @@ -2396,7 +2452,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { 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); @@ -2424,7 +2480,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { 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); @@ -2457,7 +2513,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { 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 @@ -2862,7 +2918,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { 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); diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 4f1975a23c..63e0397ec2 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -475,8 +475,13 @@ protected: /// /// @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. diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 2f30470326..e548181fb1 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -174,11 +174,12 @@ public: 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; @@ -200,13 +201,18 @@ public: // 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) { @@ -634,7 +640,8 @@ public: } 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 { diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 909d01f780..767ffcff53 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -146,7 +146,7 @@ public: std::map 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"; @@ -158,19 +158,19 @@ public: 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; @@ -260,7 +260,7 @@ public: << " 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 " @@ -383,7 +383,7 @@ public: template ReturnType retrieveOption(const Host& host, const uint16_t option_code) const { - return (retrieveOption(host, "dhcp6", option_code)); + return (retrieveOption(host, DHCP6_OPTION_SPACE, option_code)); } /// @brief Retrieve an option associated with a host. @@ -1378,6 +1378,60 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) { 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(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(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. @@ -2059,7 +2113,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { 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. @@ -2070,7 +2124,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { // 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. @@ -2121,7 +2175,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { checkResult(status, 0); def = CfgMgr::instance().getStagingCfg()-> - getCfgOptionDef()->get("dhcp6", 63); + getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 63); ASSERT_TRUE(def); // Check the option data. @@ -2162,10 +2216,10 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsGlobal) { 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. @@ -2231,13 +2285,13 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsSubnet) { // 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. @@ -2324,7 +2378,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) { // 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. @@ -2456,13 +2510,13 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) { // 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()); @@ -2514,7 +2568,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) { 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. @@ -2540,7 +2594,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) { 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>(); @@ -2711,7 +2765,7 @@ TEST_F(Dhcp6ParserTest, optionDataBoolean) { // Create configuration. Use standard option 1000. std::map params; params["name"] = "bool-option"; - params["space"] = "dhcp6"; + params["space"] = DHCP6_OPTION_SPACE; params["code"] = "1000"; params["data"] = "true"; params["csv-format"] = "true"; @@ -2857,7 +2911,7 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) { 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. @@ -2885,7 +2939,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) { ConstElementPtr x; std::map 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"; @@ -2900,7 +2954,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) { 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. @@ -3155,12 +3209,12 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) { 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()); diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 99e83b774b..f7eb9c8f87 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1838,7 +1838,7 @@ TEST_F(Dhcpv6SrvTest, relayOverride) { /// @param payload specified payload (0 = fill payload with repeating option code) /// @return RSOO with nested options OptionPtr createRSOO(const std::vector& 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"); } diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index c83850804d..808bfa2e1c 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/src/bin/dhcp6/tests/renew_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,13 @@ namespace { /// - 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\": {" @@ -188,7 +196,29 @@ const char* RENEW_CONFIGS[] = { " \"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. @@ -269,6 +299,91 @@ TEST_F(RenewTest, requestPrefixInRenew) { 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 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 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 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 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(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText()); + EXPECT_EQ(120, static_cast(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) { diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index 228105ac20..6a9e6960e0 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,11 @@ namespace { /// - 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\": {" @@ -153,7 +159,28 @@ const char* CONFIGS[] = { " \"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, @@ -305,6 +332,46 @@ TEST_F(SARRTest, optionsInheritance) { 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(option); + ASSERT_TRUE(ia); + option = ia->getOption(D6O_IAPREFIX); + ASSERT_TRUE(option); + Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_option); + option = pd_option->getOption(D6O_PD_EXCLUDE); + ASSERT_TRUE(option); + Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText()); + EXPECT_EQ(120, static_cast(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) { diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 87b64961f3..199e34071f 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -25,6 +25,7 @@ libkea_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h 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 diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h index 894a2bd627..65f636f96c 100644 --- a/src/lib/dhcp/dhcp6.h +++ b/src/lib/dhcp/dhcp6.h @@ -79,10 +79,10 @@ #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 */ @@ -104,14 +104,14 @@ //#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 */ diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h index 77368134ea..7d068407b6 100644 --- a/src/lib/dhcp/docsis3_option_defs.h +++ b/src/lib/dhcp/docsis3_option_defs.h @@ -1,4 +1,4 @@ -// 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 @@ -58,7 +58,8 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = { }; /// 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; diff --git a/src/lib/dhcp/duid_factory.cc b/src/lib/dhcp/duid_factory.cc index e042052622..b5563f07c6 100644 --- a/src/lib/dhcp/duid_factory.cc +++ b/src/lib/dhcp/duid_factory.cc @@ -1,4 +1,4 @@ -// 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 @@ -28,7 +28,7 @@ const size_t DUID_TYPE_LEN = 2; /// @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. diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 116c61dc26..033a66e2f6 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -45,8 +45,13 @@ OptionDefContainerPtr LibDHCP::v4option_defs_(new OptionDefContainer()); // 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. @@ -70,23 +75,28 @@ void initOptionSpace(OptionDefContainerPtr& defs, 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& @@ -127,8 +137,8 @@ LibDHCP::getVendorOption6Defs(const uint32_t vendor_id) { } 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) { @@ -138,18 +148,16 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) { } 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) { @@ -249,34 +257,6 @@ LibDHCP::commitRuntimeOptionDefs() { 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, @@ -312,7 +292,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, 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); @@ -448,7 +428,7 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf, 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); @@ -832,27 +812,42 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u, 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 diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index 2ac6ea49c0..31c589803b 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -27,38 +27,35 @@ public: /// Map of factory functions. typedef std::map 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 @@ -115,21 +112,6 @@ public: 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 @@ -377,6 +359,9 @@ private: /// 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_; diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc index 1c11062b8c..2d73312922 100644 --- a/src/lib/dhcp/option6_ia.cc +++ b/src/lib/dhcp/option6_ia.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -24,13 +25,13 @@ namespace dhcp { 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, @@ -43,7 +44,7 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin, "a different layout"); } - setEncapsulatedSpace("dhcp6"); + setEncapsulatedSpace(DHCP6_OPTION_SPACE); unpack(begin, end); } diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc index a392450ddc..33affd8bdd 100644 --- a/src/lib/dhcp/option6_iaaddr.cc +++ b/src/lib/dhcp/option6_iaaddr.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr 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"); } @@ -38,7 +39,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr 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); } diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc index e8e9cc82fb..4bfa672f9b 100644 --- a/src/lib/dhcp/option6_iaprefix.cc +++ b/src/lib/dhcp/option6_iaprefix.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -28,7 +29,7 @@ namespace dhcp { 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(prefix_len) @@ -40,7 +41,7 @@ Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& 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); } diff --git a/src/lib/dhcp/option6_pdexclude.cc b/src/lib/dhcp/option6_pdexclude.cc new file mode 100644 index 0000000000..a29b7bbfec --- /dev/null +++ b/src/lib/dhcp/option6_pdexclude.cc @@ -0,0 +1,227 @@ +// 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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(delegated_prefix_length_) << ", " + << excluded_prefix_ << "/" + << static_cast(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(excluded_prefix_length_) + << " must be greater than the length of the" + " delegated prefix " << delegated_prefix_ << "/" + << static_cast(delegated_prefix_length_)); + } + + // Both prefixes must share common part with a length equal to the + // delegated prefix length. + std::vector delegated_prefix_bytes = delegated_prefix_.toBytes(); + boost::dynamic_bitset delegated_prefix_bits(delegated_prefix_bytes.rbegin(), + delegated_prefix_bytes.rend()); + + std::vector excluded_prefix_bytes = excluded_prefix_.toBytes(); + boost::dynamic_bitset 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(excluded_prefix_length_) + << " must have the same common prefix part of " + << static_cast(delegated_prefix_length) + << " as the delegated prefix " + << delegated_prefix_ << "/" + << static_cast(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()); +} + +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 excluded_prefix_bytes = excluded_prefix_.toBytes(); + boost::dynamic_bitset 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 first_byte = bits >> 120; + // Convert it to a numeric value. + uint8_t val = static_cast(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 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(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 bits(delegated_prefix_bytes.rbegin(), + delegated_prefix_bytes.rend()); + + // Convert subnet id to bit format. + std::vector subnet_id_bytes(begin, end); + boost::dynamic_bitset 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 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(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 diff --git a/src/lib/dhcp/option6_pdexclude.h b/src/lib/dhcp/option6_pdexclude.h new file mode 100644 index 0000000000..ffcfd1566e --- /dev/null +++ b/src/lib/dhcp/option6_pdexclude.h @@ -0,0 +1,134 @@ +// 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 +#include +#include +#include + +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 Option6PDExcludePtr; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION6_PDEXCLUDE_H diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index e9ad45b5c7..643459f11a 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -10,6 +10,8 @@ #include #include +using namespace isc::asiolink; + namespace isc { namespace dhcp { @@ -46,7 +48,7 @@ OptionCustom::clone() const { } void -OptionCustom::addArrayDataField(const asiolink::IOAddress& address) { +OptionCustom::addArrayDataField(const IOAddress& address) { checkArrayType(); if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) || @@ -71,6 +73,36 @@ OptionCustom::addArrayDataField(const bool value) { 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()) { @@ -110,10 +142,17 @@ OptionCustom::createBuffers() { // 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 @@ -135,9 +174,15 @@ OptionCustom::createBuffers() { // 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. @@ -191,7 +236,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // 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 @@ -199,19 +244,28 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // 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(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. @@ -253,6 +307,13 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // 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 @@ -284,11 +345,17 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { // 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 + (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 { @@ -383,7 +450,7 @@ OptionCustom::pack(isc::util::OutputBuffer& buf) const { } -asiolink::IOAddress +IOAddress OptionCustom::readAddress(const uint32_t index) const { checkIndex(index); @@ -402,10 +469,8 @@ OptionCustom::readAddress(const uint32_t index) const { } 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) || @@ -471,6 +536,45 @@ OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) { 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); diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h index ae8009f9c1..122da7c9b9 100644 --- a/src/lib/dhcp/option_custom.h +++ b/src/lib/dhcp/option_custom.h @@ -7,6 +7,7 @@ #ifndef OPTION_CUSTOM_H #define OPTION_CUSTOM_H +#include #include #include #include @@ -112,6 +113,19 @@ public: 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. @@ -228,6 +242,45 @@ public: 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. diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc index 8a8fdf622d..55a0145c67 100644 --- a/src/lib/dhcp/option_data_types.cc +++ b/src/lib/dhcp/option_data_types.cc @@ -1,4 +1,4 @@ -// 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 @@ -8,6 +8,9 @@ #include #include #include +#include + +using namespace isc::asiolink; namespace isc { namespace dhcp { @@ -24,6 +27,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() { 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; @@ -39,6 +44,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() { 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"; @@ -86,6 +93,9 @@ OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) { case OPT_IPV6_ADDRESS_TYPE: return (asiolink::V6ADDRESS_LEN); + case OPT_PSID_TYPE: + return (3); + default: ; } @@ -237,6 +247,207 @@ OptionDataTypeUtil::getLabelCount(const std::string& text_name) { } } +PrefixTuple +OptionDataTypeUtil::readPrefix(const std::vector& 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((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 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& 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((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 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& 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(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(static_cast(0xFFFF << psid_len) + >> psid_len)) != 0)) { + isc_throw(BadDataTypeCast, "invalid PSID value " << psid + << " for a specified PSID length " + << static_cast(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& 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 + (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())), + &buf[buf.size() - 2], 2); +} + + std::string OptionDataTypeUtil::readString(const std::vector& buf) { std::string value; diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index e7a5dc8ebd..3aa56080e3 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -1,4 +1,4 @@ -// 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 @@ -13,6 +13,7 @@ #include #include +#include namespace isc { namespace dhcp { @@ -53,12 +54,33 @@ enum OptionDataType { 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 @@ -173,6 +195,123 @@ struct OptionDataTypeTraits { 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(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 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(prefix_len_)); + } + +private: + + /// @brief Prefix length. + uint8_t prefix_len_; +}; + +/// @brief Defines a pair of prefix length / value. +typedef std::pair PrefixTuple; + /// @brief Utility class for option data types. /// /// This class provides a set of utility functions to operate on @@ -221,7 +360,7 @@ public: /// /// @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. @@ -378,6 +517,59 @@ public: /// @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& 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& 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& 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& buf); + /// @brief Read string value from a buffer. /// /// @param buf input buffer. diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc index 22744b5f28..30f1a4370c 100644 --- a/src/lib/dhcp/option_definition.cc +++ b/src/lib/dhcp/option_definition.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include #include #include +#include using namespace std; using namespace isc::util; @@ -565,6 +565,87 @@ OptionDefinition::writeToBuffer(const std::string& value, 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(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(txt_psid); + } catch (...) { + isc_throw(BadDataTypeCast, "provided PSID " + << txt_psid << " is not valid"); + } + + try { + psid_len = lexicalCastWithRangeCheck(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; diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h index a3b94e8ba7..b90f5d5a79 100644 --- a/src/lib/dhcp/option_definition.h +++ b/src/lib/dhcp/option_definition.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace isc { namespace dhcp { @@ -117,7 +118,9 @@ class OptionIntArray; /// - "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) @@ -717,6 +720,9 @@ typedef boost::multi_index_container< /// Pointer to an option definition container. typedef boost::shared_ptr OptionDefContainerPtr; +/// Container that holds option definitions for various option spaces. +typedef std::map OptionDefContainers; + /// Container that holds various vendor option containers typedef std::map VendorOptionDefContainers; diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h index 1a13050845..05224307f1 100644 --- a/src/lib/dhcp/option_int.h +++ b/src/lib/dhcp/option_int.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ public: if (!OptionDataTypeTraits::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. @@ -90,7 +91,7 @@ public: if (!OptionDataTypeTraits::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); } diff --git a/src/lib/dhcp/option_space.h b/src/lib/dhcp/option_space.h index c1867d930d..96c401d184 100644 --- a/src/lib/dhcp/option_space.h +++ b/src/lib/dhcp/option_space.h @@ -1,4 +1,4 @@ -// 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 @@ -13,8 +13,13 @@ #include #include -#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 { @@ -174,7 +179,7 @@ public: void setVendorSpace(const uint32_t enterprise_number); private: - + uint32_t enterprise_number_; ///< IANA assigned enterprise number. }; diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc index b0150c4d62..2b66490d70 100644 --- a/src/lib/dhcp/pkt4.cc +++ b/src/lib/dhcp/pkt4.cc @@ -203,7 +203,7 @@ Pkt4::unpack() { // 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 diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 1658335b20..bb6c9723ec 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -403,7 +404,7 @@ Pkt6::unpackMsg(OptionBuffer::const_iterator begin, // 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 @@ -453,7 +454,7 @@ Pkt6::unpackRelayMsg() { // 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 diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h index 10935453ba..c40c2e5332 100644 --- a/src/lib/dhcp/std_option_defs.h +++ b/src/lib/dhcp/std_option_defs.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace isc { namespace dhcp { @@ -36,18 +37,6 @@ namespace { #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 @@ -72,7 +61,7 @@ RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE); 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, "" }, @@ -230,9 +219,8 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = { }; /// 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 @@ -257,6 +245,13 @@ RECORD_DECL(LQ_QUERY_RECORDS, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE); 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 @@ -280,7 +275,7 @@ RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE); /// 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), "" }, @@ -366,6 +361,7 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = { { "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, "" }, @@ -378,7 +374,14 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = { { "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 @@ -386,8 +389,17 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = { }; /// 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 @@ -395,12 +407,65 @@ const int OPTION_DEF_PARAMS_SIZE6 = /// /// @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 diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am index caf369ac81..8993829733 100644 --- a/src/lib/dhcp/tests/Makefile.am +++ b/src/lib/dhcp/tests/Makefile.am @@ -54,6 +54,7 @@ libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc 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 diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 6b3f98e8e6..227bf8e1fa 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -101,8 +101,8 @@ public: 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. @@ -125,8 +125,33 @@ public: 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. @@ -216,6 +241,7 @@ private: /// 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. @@ -225,7 +251,8 @@ private: /// 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, @@ -235,7 +262,7 @@ private: // 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>(); @@ -680,7 +707,7 @@ TEST_F(LibDhcpTest, packOptions4) { // 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. @@ -1012,69 +1039,6 @@ TEST_F(LibDhcpTest, unpackSubOptions4) { 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. @@ -1619,18 +1583,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) { 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); } @@ -1641,12 +1632,12 @@ TEST_F(LibDhcpTest, getOptionDefByName6) { // 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); } diff --git a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc new file mode 100644 index 0000000000..b7dfa62dfa --- /dev/null +++ b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc @@ -0,0 +1,175 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// Author: Andrei Pavel +// +// 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 + +#include +#include +#include +#include +#include +#include + +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 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(buf.getData()); + std::vector 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 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(option->getDelegatedPrefixLength())); + EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText()); + EXPECT_EQ(64, static_cast(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 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(option->getDelegatedPrefixLength())); + EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText()); + EXPECT_EQ(64, static_cast(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 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 diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc index bafb4e485b..f897958ff8 100644 --- a/src/lib/dhcp/tests/option_custom_unittest.cc +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -18,6 +18,11 @@ 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 OptionCustomTest test class. class OptionCustomTest : public ::testing::Test { public: @@ -476,6 +481,82 @@ TEST_F(OptionCustomTest, ipv6AddressData) { ); } +// 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(32, buf); + writeInt(0x30000001, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr 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(4, buf); + writeInt(0x8000, buf); + + // Append suboption. + appendV6Suboption(buf); + + // Create custom option using input buffer. + boost::scoped_ptr 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. @@ -768,6 +849,97 @@ TEST_F(OptionCustomTest, fqdnDataArray) { 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(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr 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(sizeof(data) / sizeof(char))); + + // Create custom option using input buffer. + boost::scoped_ptr 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. @@ -821,6 +993,7 @@ TEST_F(OptionCustomTest, recordData) { 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[] = { @@ -841,6 +1014,9 @@ TEST_F(OptionCustomTest, recordData) { 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(6, buf); + writeInt(0xD400, buf); // Initialize field 5 to string value. writeString("ABCD", buf); @@ -851,7 +1027,7 @@ TEST_F(OptionCustomTest, recordData) { 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; @@ -879,9 +1055,15 @@ TEST_F(OptionCustomTest, recordData) { 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 @@ -1073,6 +1255,64 @@ TEST_F(OptionCustomTest, setIpv6AddressData) { 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 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 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 @@ -1280,6 +1520,96 @@ TEST_F(OptionCustomTest, setIpv6AddressDataArray) { ); } +/// 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 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 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"); @@ -1288,6 +1618,8 @@ TEST_F(OptionCustomTest, setRecordData) { 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 @@ -1300,7 +1632,7 @@ TEST_F(OptionCustomTest, setRecordData) { // 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; @@ -1318,9 +1650,17 @@ TEST_F(OptionCustomTest, setRecordData) { 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(1234, 0)); @@ -1328,7 +1668,10 @@ TEST_F(OptionCustomTest, setRecordData) { 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(0)); @@ -1341,8 +1684,14 @@ TEST_F(OptionCustomTest, setRecordData) { 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 diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc index fae109272c..fae60eb82a 100644 --- a/src/lib/dhcp/tests/option_data_types_unittest.cc +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -1,4 +1,4 @@ -// 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 @@ -7,12 +7,19 @@ #include #include #include +#include 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: @@ -456,6 +463,258 @@ TEST_F(OptionDataTypesTest, writeFqdn) { ); } +// 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 buf; + + // Prefix 2001:db8::/64 + writeInt(64, buf); + writeInt(0x20010db8, buf); + writeInt(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(63, buf); + writeInt(0x20010db8, buf); + writeInt(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(34, buf); + writeInt(0x20010db8, buf); + writeInt(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(0, buf); + writeInt(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(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(129, buf); + writeInt(0x3000, buf); + buf.resize(17); + + EXPECT_THROW(static_cast(OptionDataTypeUtil::readPrefix(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer truncated. Prefix length of 10 requires at least 2 bytes, + // but there is only one byte. + writeInt(10, buf); + writeInt(1, buf); + + EXPECT_THROW(static_cast(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 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(buf[0])); + EXPECT_EQ(34, static_cast(buf[1])); + EXPECT_EQ(0x20, static_cast(buf[2])); + EXPECT_EQ(0x01, static_cast(buf[3])); + EXPECT_EQ(0x0D, static_cast(buf[4])); + EXPECT_EQ(0xB8, static_cast(buf[5])); + EXPECT_EQ(0xC0, static_cast(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(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 buf; + + // PSID length is 6 (bits) + writeInt(6, buf); + // 0xA400 is represented as 1010010000000000b, which is equivalent + // of portset 0x29 (101001b). + writeInt(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(0, buf); + // Let's put some junk into the PSID field to make sure it will + // be ignored. + writeInt(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(17, buf); + writeInt(0, buf); + EXPECT_THROW(static_cast(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(3, buf); + writeInt(0xB000, buf); + EXPECT_THROW(static_cast(OptionDataTypeUtil::readPsid(buf)), + BadDataTypeCast); + + buf.clear(); + + // Buffer is truncated - 2 bytes instead of 3. + writeInt(4, buf); + writeInt(0xF0, buf); + EXPECT_THROW(static_cast(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 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(buf[0])); + // PSID length should be written as specified in the function call. + EXPECT_EQ(4, static_cast(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(buf[2])); + EXPECT_EQ(0x00, static_cast(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(buf[0])); + EXPECT_EQ(0, static_cast(buf[1])); + EXPECT_EQ(0, static_cast(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(buf[0])); + // Check PSID value. + EXPECT_EQ(0x00, static_cast(buf[1])); + EXPECT_EQ(0x05, static_cast(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) { diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc index 00f4452bfb..fbb67a9a3e 100644 --- a/src/lib/dhcp/tests/option_definition_unittest.cc +++ b/src/lib/dhcp/tests/option_definition_unittest.cc @@ -1241,7 +1241,7 @@ TEST_F(OptionDefinitionTest, integerInvalidType) { // see if it rejects it. OptionBuffer buf(1); EXPECT_THROW( - OptionDefinition::factoryInteger(Option::V6, D6O_PREFERENCE, "dhcp6", + OptionDefinition::factoryInteger(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE, buf.begin(), buf.end()), isc::dhcp::InvalidDataType ); @@ -1289,4 +1289,225 @@ TEST_F(OptionDefinitionTest, haveClientFqdnFormat) { 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(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 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(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 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(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(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 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(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 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(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 diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc index 247a53d52a..c9fc49d762 100644 --- a/src/lib/dhcp/tests/option_unittest.cc +++ b/src/lib/dhcp/tests/option_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -593,15 +594,14 @@ TEST_F(OptionTest, setEncapsulatedSpace) { 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 diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 9463f8cf92..c78574efc7 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -350,7 +350,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, 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) { @@ -688,10 +688,11 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // non-PD leases. uint8_t prefix_len = 128; if (ctx.currentIA().type_ == Lease::TYPE_PD) { - Pool6Ptr pool = boost::dynamic_pointer_cast( + pool = boost::dynamic_pointer_cast( 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_, diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index c28775666c..61958fab4f 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -397,6 +397,10 @@ public: /// @brief Container holding IA specific contexts. std::vector 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. diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index b901d6030d..8947dd5335 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -105,19 +105,31 @@ CfgOption::encapsulateInternal(const std::string& option_space) { // 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_); } } } diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 065316fa6a..61f3f13318 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -409,6 +409,16 @@ private: /// 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 diff --git a/src/lib/dhcpsrv/cfg_option_def.cc b/src/lib/dhcpsrv/cfg_option_def.cc index ccc668ae85..1dd51a5729 100644 --- a/src/lib/dhcpsrv/cfg_option_def.cc +++ b/src/lib/dhcpsrv/cfg_option_def.cc @@ -89,12 +89,7 @@ CfgOptionDef::add(const OptionDefinitionPtr& def, " 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 << "'"); diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 2c8123681f..b829748c93 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -840,13 +840,8 @@ private: // 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. diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index b96416cf4b..7fa89123f9 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -525,7 +525,8 @@ OptionDataParser::extractCSVFormat() const { 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"); @@ -565,21 +566,15 @@ template 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); } } @@ -838,7 +833,8 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) { 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", ""); diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index 13d3c05d02..38d594112b 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -76,7 +76,9 @@ Pool4::Pool4( const isc::asiolink::IOAddress& prefix, uint8_t prefix_len) 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()) { @@ -117,21 +119,91 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first, } 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(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(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(excluded_prefix_len_) + << " must be lower than the delegated prefix length " + << static_cast(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(prefix_len)); + isc_throw(BadValue, "Invalid prefix length: " + << static_cast(prefix_len)); } if (prefix_len > delegated_len) { - isc_throw(BadValue, "Delegated length (" << static_cast(delegated_len) + isc_throw(BadValue, "Delegated length (" + << static_cast(delegated_len) << ") must be longer than or equal to prefix length (" << static_cast(prefix_len) << ")"); } @@ -142,6 +214,13 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix, << " 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(excluded_prefix_len) + << ") must be longer than the delegated prefix length (" + << static_cast(delegated_len)); + } + /// @todo: We should probably implement checks against weird addresses /// here, like ::, starting with fe80, starting with ff etc. . @@ -156,11 +235,17 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix, std::string Pool6::toText() const { - std::stringstream tmp; - tmp << "type=" << Lease::typeToText(type_) << ", " << first_ - << "-" << last_ << ", delegated_len=" - << static_cast(prefix_len_); - return (tmp.str()); + std::ostringstream s; + s << "type=" << Lease::typeToText(type_) << ", " << first_ + << "-" << last_ << ", delegated_len=" + << static_cast(prefix_len_); + + if (excluded_prefix_len_ > 0) { + s << ", excluded_prefix=" << excluded_prefix_ + << ", excluded_prefix_len=" + << static_cast(excluded_prefix_len_); + } + return (s.str()); } }; // end of isc::dhcp namespace diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h index 6a9747f255..a4c500287a 100644 --- a/src/lib/dhcpsrv/pool.h +++ b/src/lib/dhcpsrv/pool.h @@ -213,10 +213,27 @@ public: /// @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 @@ -233,14 +250,75 @@ public: 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 diff --git a/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc index 67a0e22a03..cb77858781 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc @@ -182,7 +182,7 @@ TEST(CfgOptionDefTest, get) { // 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")); diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index adaccf4da2..0eb015e441 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -22,8 +23,73 @@ using namespace isc::dhcp; 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; @@ -33,7 +99,7 @@ TEST(CfgOptionTest, empty) { // 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 @@ -44,7 +110,7 @@ TEST(CfgOptionTest, empty) { } // This test verifies that the option configurations can be compared. -TEST(CfgOptionTest, equals) { +TEST_F(CfgOptionTest, equals) { CfgOption cfg1; CfgOption cfg2; @@ -97,13 +163,13 @@ TEST(CfgOptionTest, equals) { // 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 @@ -114,7 +180,7 @@ TEST(CfgOptionTest, add) { } // 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()); @@ -147,7 +213,7 @@ TEST(CfgOptionTest, add) { } // This test verifies that two option configurations can be merged. -TEST(CfgOptionTest, merge) { +TEST_F(CfgOptionTest, merge) { CfgOption cfg_src; CfgOption cfg_dst; @@ -155,7 +221,7 @@ TEST(CfgOptionTest, merge) { // 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 @@ -172,7 +238,7 @@ TEST(CfgOptionTest, merge) { // 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 @@ -189,7 +255,7 @@ TEST(CfgOptionTest, merge) { // 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 @@ -222,7 +288,7 @@ TEST(CfgOptionTest, merge) { // 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) { @@ -262,60 +328,71 @@ TEST(CfgOptionTest, copy) { // 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 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(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 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); + } } } } @@ -323,13 +400,13 @@ TEST(CfgOptionTest, encapsulate) { // 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 @@ -341,7 +418,7 @@ TEST(CfgOptionTest, get) { // 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()); @@ -350,7 +427,7 @@ TEST(CfgOptionTest, get) { // 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. @@ -358,12 +435,12 @@ TEST(CfgOptionTest, addNonUniqueOptions) { // 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. @@ -412,11 +489,11 @@ TEST(Subnet6Test, addPersistentOption) { // 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. @@ -438,7 +515,7 @@ TEST(Subnet6Test, addPersistentOption) { } // 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) @@ -494,7 +571,7 @@ TEST(CfgOptionTest, addVendorOptions) { // 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. diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc index 5949cdbb43..bdfcae94fe 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc @@ -283,7 +283,7 @@ TEST_F(ClientClassDefParserTest, nameOnlyValid) { 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. @@ -314,7 +314,7 @@ TEST_F(ClientClassDefParserTest, nameAndExpressionClass) { 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 @@ -357,7 +357,7 @@ TEST_F(ClientClassDefParserTest, nameAndOptionsClass) { 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()); @@ -393,7 +393,7 @@ TEST_F(ClientClassDefParserTest, basicValidClass) { 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()); diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc index be98a2ba3a..0ac90d78a2 100644 --- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc +++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -69,13 +70,13 @@ TEST(ClientClassDef, cfgOptionBasics) { 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))); @@ -83,7 +84,7 @@ TEST(ClientClassDef, cfgOptionBasics) { 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()); @@ -91,7 +92,7 @@ TEST(ClientClassDef, cfgOptionBasics) { 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()); } @@ -113,7 +114,7 @@ TEST(ClientClassDef, copyAndEquality) { 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, @@ -176,7 +177,7 @@ TEST(ClientClassDef, copyAndEquality) { // 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)); diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index 6cff215133..eeb7c96b87 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -31,6 +31,7 @@ using namespace std; using namespace isc; +using namespace isc::asiolink; using namespace isc::config; using namespace isc::data; using namespace isc::dhcp; @@ -571,7 +572,7 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) { // 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. @@ -683,7 +684,7 @@ TEST_F(ParseConfigTest, escapedOptionDataTest) { 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); @@ -721,7 +722,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) { // 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()); @@ -741,7 +742,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) { // 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()); @@ -761,11 +762,55 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) { // 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 + (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(0); + ea_len = s46_rule->readInteger(1); + prefix4_len = s46_rule->readInteger(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. @@ -816,7 +861,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { "}"; 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]); @@ -835,7 +880,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) { "}"; 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]); @@ -857,7 +902,7 @@ TEST_F(ParseConfigTest, optionDataNoName) { 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()); @@ -877,7 +922,7 @@ TEST_F(ParseConfigTest, optionDataNoCode) { 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()); @@ -896,7 +941,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) { 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()); @@ -912,7 +957,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) { rcode = 0; ASSERT_NO_THROW(rcode = parseConfiguration(config)); EXPECT_EQ(0, rcode); - opt = boost::dynamic_pointer_cast(getOptionPtr("dhcp6", + opt = boost::dynamic_pointer_cast(getOptionPtr(DHCP6_OPTION_SPACE, 23)); ASSERT_TRUE(opt); ASSERT_EQ(1, opt->getAddresses().size()); @@ -942,7 +987,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) { 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()); @@ -967,7 +1012,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) { rcode = 0; ASSERT_NO_THROW(rcode = parseConfiguration(config)); EXPECT_EQ(0, rcode); - opt = boost::dynamic_pointer_cast(getOptionPtr("dhcp6", + opt = boost::dynamic_pointer_cast(getOptionPtr(DHCP6_OPTION_SPACE, 2345)); ASSERT_TRUE(opt); ASSERT_EQ(2, opt->getAddresses().size()); @@ -989,7 +1034,7 @@ TEST_F(ParseConfigTest, emptyOptionData) { 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()); } @@ -1009,7 +1054,7 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) { 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()); } diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc index ba624209ee..43a87fb860 100644 --- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc index 5c05dd2eb2..5c72c91149 100644 --- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc @@ -77,13 +77,13 @@ protected: 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. @@ -779,7 +779,7 @@ TEST_F(HostReservationParserTest, options4) { // 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()); @@ -788,7 +788,7 @@ TEST_F(HostReservationParserTest, options4) { // 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()); @@ -796,7 +796,7 @@ TEST_F(HostReservationParserTest, options4) { // 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()); } @@ -837,7 +837,7 @@ TEST_F(HostReservationParserTest, options6) { // 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()); @@ -846,7 +846,7 @@ TEST_F(HostReservationParserTest, options6) { // 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()); @@ -854,7 +854,7 @@ TEST_F(HostReservationParserTest, options6) { // 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()); } diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc index e51733bbb1..a2477879e1 100644 --- a/src/lib/dhcpsrv/tests/host_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_unittest.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -769,7 +770,7 @@ TEST_F(HostTest, addOptions4) { // 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 @@ -780,20 +781,20 @@ TEST_F(HostTest, addOptions4) { } // 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()); @@ -833,7 +834,7 @@ TEST_F(HostTest, addOptions6) { // 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 @@ -844,20 +845,20 @@ TEST_F(HostTest, addOptions6) { } // 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()); diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc index 72d31eb2c0..0e08bd4f7a 100644 --- a/src/lib/dhcpsrv/tests/pool_unittest.cc +++ b/src/lib/dhcpsrv/tests/pool_unittest.cc @@ -206,6 +206,68 @@ TEST(Pool6Test, PD) { 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(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(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 @@ -261,7 +323,7 @@ TEST(Pool6Test, unique_id) { } // 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", @@ -270,6 +332,13 @@ TEST(Poo6Test,toText) { 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. diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc index 98b8fa665f..1890157f0a 100644 --- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc +++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc @@ -284,7 +284,7 @@ TEST_F(SrvConfigTest, copy) { // 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_); diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index 46947062e8..00277013fb 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -350,7 +351,7 @@ TEST(Subnet4Test, addInvalidOption) { // 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); } @@ -870,7 +871,7 @@ TEST(Subnet6Test, addOptions) { // 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 @@ -881,7 +882,7 @@ TEST(Subnet6Test, addOptions) { } // 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()); @@ -922,12 +923,12 @@ TEST(Subnet6Test, addNonUniqueOptions) { // 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. @@ -974,11 +975,11 @@ TEST(Subnet6Test, addPersistentOption) { // 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. @@ -1005,7 +1006,7 @@ TEST(Subnet6Test, getOptions) { // 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 @@ -1017,7 +1018,7 @@ TEST(Subnet6Test, getOptions) { // 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()); diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc index 8cd13653b5..d90d71bee5 100644 --- a/src/lib/eval/eval_context.cc +++ b/src/lib/eval/eval_context.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -79,11 +80,12 @@ uint16_t 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); } -- 2.47.3