diff --git a/Makefile.am b/Makefile.am index 2c0733c29c..c23fe48402 100644 --- a/Makefile.am +++ b/Makefile.am @@ -110,8 +110,8 @@ endif --output report.info; \ sed --in-place --expression "s|$(abs_top_srcdir)|$(abs_top_builddir)|g" report.info; \ "$(GENHTML)" --frames --show-details --title 'Kea code coverage report' --legend \ - --function-coverage --ignore-errors source --demangle-cpp \ - --output "$(OVERALL_COVERAGE_DIR)" report.info; \ + --function-coverage --ignore-errors source --demangle-cpp \ + --output "$(OVERALL_COVERAGE_DIR)" report.info; \ printf "Generated C++ code coverage report in HTML at %s.\n" "$(OVERALL_COVERAGE_DIR)"; \ else \ echo "C++ code coverage not enabled at configuration time." ; \ diff --git a/configure.ac b/configure.ac index 00842da9bc..ea7e26cbe6 100644 --- a/configure.ac +++ b/configure.ac @@ -62,15 +62,15 @@ AC_SUBST(SEP) # If cross compiling assume the message compiler executable was # magically already in place... if test "$cross_compiling" = "yes"; then - AC_MSG_CHECKING("build (vs. host) compiled message compiler") - if test -x "${srcdir}/src/lib/log/compiler/message"; then - AC_MSG_RESULT(yes) - else - AC_MSG_RESULT(no) - AC_MSG_WARN("you must install a message compiler in:") - AC_MSG_WARN(" ${srcdir}/src/lib/log/compiler/message") - AC_MSG_WARN("compiled for build ($build).") - fi + AC_MSG_CHECKING("build (vs. host) compiled message compiler") + if test -x "${srcdir}/src/lib/log/compiler/message"; then + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + AC_MSG_WARN("you must install a message compiler in:") + AC_MSG_WARN(" ${srcdir}/src/lib/log/compiler/message") + AC_MSG_WARN("compiled for build ($build).") + fi fi AM_CONDITIONAL([CROSS_COMPILING], [test "$cross_compiling" = "yes"]) @@ -1501,6 +1501,7 @@ AC_CONFIG_FILES([Makefile src/share/database/scripts/mysql/upgrade_5.0_to_5.1.sh src/share/database/scripts/mysql/upgrade_5.1_to_5.2.sh src/share/database/scripts/mysql/upgrade_5.2_to_6.0.sh + src/share/database/scripts/mysql/upgrade_6.0_to_7.0.sh src/share/database/scripts/pgsql/Makefile src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh @@ -1672,9 +1673,9 @@ if test "$CQL_CPPFLAGS" != "" ; then cat >> config.report << END Cassandra CQL: - CQL_VERSION: ${CQL_VERSION} - CQL_CPPFLAGS: ${CQL_CPPFLAGS} - CQL_LIBS: ${CQL_LIBS} + CQL_VERSION: ${CQL_VERSION} + CQL_CPPFLAGS: ${CQL_CPPFLAGS} + CQL_LIBS: ${CQL_LIBS} END else cat >> config.report << END diff --git a/doc/guide/intro.xml b/doc/guide/intro.xml index 1d0d1f32c4..d15e70cef7 100644 --- a/doc/guide/intro.xml +++ b/doc/guide/intro.xml @@ -79,7 +79,7 @@ - In order to store lease information in a MySQL database, Kea requires MySQL + In order to store lease information in a MySQL database, Kea requires MySQL headers and libraries. This is an optional dependency in that Kea can be built without MySQL support. @@ -87,7 +87,7 @@ - In order to store lease information in a PostgreSQL database, Kea requires PostgreSQL + In order to store lease information in a PostgreSQL database, Kea requires PostgreSQL headers and libraries. This is an optional dependency in that Kea can be built without PostgreSQL support. @@ -95,7 +95,7 @@ - In order to store lease information in a Cassandra database (CQL), Kea + In order to store lease information in a Cassandra database (CQL), Kea requires Cassandra headers and libraries. This is an optional dependency in that Kea can be built without Cassandra support. @@ -163,7 +163,7 @@ kea-lfc — This process removes redundant information from the files used to provide persistent storage for the memfile data base backend. - While it can be run standalone, it is normally run as and when + While it can be run standalone, it is normally run as and when required by the Kea DHCP servers. diff --git a/doc/guide/lfc.xml b/doc/guide/lfc.xml index e689bde5aa..b90fe696bc 100644 --- a/doc/guide/lfc.xml +++ b/doc/guide/lfc.xml @@ -15,7 +15,7 @@ kea-lfc is a service process that removes redundant information from the files used to provide persistent storage for the memfile data base backend. This service is written to run as a - stand alone process. + stand alone process. While kea-lfc can be started externally, there is usually no need to do this. kea-lfc is run on a periodic diff --git a/src/bin/admin/tests/cql_tests.sh.in b/src/bin/admin/tests/cql_tests.sh.in index 0478fd87f8..a9bd14d115 100644 --- a/src/bin/admin/tests/cql_tests.sh.in +++ b/src/bin/admin/tests/cql_tests.sh.in @@ -144,23 +144,23 @@ cql_lease4_dump_test() { assert_eq 0 $? "kea-admin lease-init cql failed, expected exit code: %d, actual: %d" # Insert the reference record. - # -1073741302 corresponds to 192.0.2.10 - # -1073741301 corresponds to 192.0.2.11 - # -1073741300 corresponds to 192.0.2.12 + # 3221225994 corresponds to 192.0.2.10 + # 3221225995 corresponds to 192.0.2.11 + # 3221225996 corresponds to 192.0.2.12 # 1430694930 corresponds to 2015-04-04 01:15:30 # 1433464245 corresponds to 2015-05-05 02:30:45 # 1436173267 corresponds to 2015-06-06 11:01:07 insert_cql="\ INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\ fqdn_fwd, fqdn_rev, hostname, state)\ - VALUES(-1073741302,textAsBlob('20'),textAsBlob('30'),40,1430694930,50,true,true,\ + VALUES(3221225994,textAsBlob('20'),textAsBlob('30'),40,1430694930,50,true,true,\ 'one.example.com', 0);\ INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\ fqdn_fwd, fqdn_rev, hostname, state)\ - VALUES(-1073741301,NULL,textAsBlob('123'),40,1433464245,50,true,true,'', 1);\ + VALUES(3221225995,NULL,textAsBlob('123'),40,1433464245,50,true,true,'', 1);\ INSERT INTO lease4(address, hwaddr, client_id, valid_lifetime, expire, subnet_id,\ fqdn_fwd, fqdn_rev, hostname, state)\ - VALUES(-1073741300,textAsBlob('22'),NULL,40,1436173267,50,true,true,\ + VALUES(3221225996,textAsBlob('22'),NULL,40,1436173267,50,true,true,\ 'three.example.com', 2);" cql_execute "$insert_cql" diff --git a/src/bin/admin/tests/data/cql.lease4_dump_test.reference.csv b/src/bin/admin/tests/data/cql.lease4_dump_test.reference.csv index 5316f1e734..95dc3c3bc4 100644 --- a/src/bin/admin/tests/data/cql.lease4_dump_test.reference.csv +++ b/src/bin/admin/tests/data/cql.lease4_dump_test.reference.csv @@ -1,4 +1,4 @@ address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state --1073741302,0x3230,0x3330,40,1430694930,50,True,True,one.example.com,0 --1073741301,null,0x313233,40,1433464245,50,True,True,,1 --1073741300,0x3232,null,40,1436173267,50,True,True,three.example.com,2 +3221225996,0x3232,null,40,1436173267,50,True,True,three.example.com,2 +3221225995,null,0x313233,40,1433464245,50,True,True,,1 +3221225994,0x3230,0x3330,40,1430694930,50,True,True,one.example.com,0 diff --git a/src/bin/admin/tests/dhcpdb_create_1.0.cql b/src/bin/admin/tests/dhcpdb_create_1.0.cql index 577f2ae277..9c5c8dac7c 100644 --- a/src/bin/admin/tests/dhcpdb_create_1.0.cql +++ b/src/bin/admin/tests/dhcpdb_create_1.0.cql @@ -47,7 +47,7 @@ -- Table `lease4` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS lease4 ( - address int, + address bigint, hwaddr blob, client_id blob, valid_lifetime bigint, @@ -133,6 +133,8 @@ CREATE TABLE IF NOT EXISTS lease_hwaddr_source ( PRIMARY KEY ((hwaddr_source)) ); +INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (0, 'HWADDR_SOURCE_UNKNOWN'); + -- Hardware address obtained from raw sockets INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (1, 'HWADDR_SOURCE_RAW'); @@ -154,6 +156,8 @@ INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (32, 'HWADDR_SOURCE -- Hardware address extracted from docsis options INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (64, 'HWADDR_SOURCE_DOCSIS_CMTS'); +INSERT INTO lease_hwaddr_source (hwaddr_source, name) VALUES (128, 'HWADDR_SOURCE_DOCSIS_MODEM'); + -- Create table holding mapping of the lease states to their names. -- This is not used in queries from the DHCP server but rather in -- direct queries from the lease database management tools. diff --git a/src/bin/admin/tests/dhcpdb_create_1.0.mysql b/src/bin/admin/tests/dhcpdb_create_1.0.mysql index b4823afbff..9f68439ffe 100644 --- a/src/bin/admin/tests/dhcpdb_create_1.0.mysql +++ b/src/bin/admin/tests/dhcpdb_create_1.0.mysql @@ -123,7 +123,7 @@ COMMIT; # # Portability # =========== -# The "ENGINE = INNODB" on some tables is not portablea to another database +# The "ENGINE = INNODB" on some tables is not portable to another database # and will need to be removed. # # Some columns contain binary data so are stored as VARBINARY instead of diff --git a/src/bin/admin/tests/mysql_tests.sh.in b/src/bin/admin/tests/mysql_tests.sh.in index 85c1a9558f..94e96ddbe8 100644 --- a/src/bin/admin/tests/mysql_tests.sh.in +++ b/src/bin/admin/tests/mysql_tests.sh.in @@ -449,9 +449,16 @@ EOF # lease4/6_stats changes are tested separately - # Verify upgraded schema reports version 6.0 + # table: lease4 (upgrade 6.0 -> 7.0) + qry="SELECT address FROM lease4" + text=`mysql_execute "${qry}"` + ERRCODE=$? + assert_eq 0 $ERRCODE "lease4 table is missing or broken. (expected status code %d, returned %d)" + + # Verify upgraded schema reports version 7. + version=$(${keaadmin} lease-version mysql -u $db_user -p $db_password -n $db_name -d $db_scripts_dir) - assert_str_eq "6.0" ${version} "Expected kea-admin to return %s, returned value was %s" + assert_str_eq "7.0" ${version} "Expected kea-admin to return %s, returned value was %s" # Let's wipe the whole database mysql_wipe diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc index c3e87f037f..265388d3d9 100644 --- a/src/bin/d2/d2_cfg_mgr.cc +++ b/src/bin/d2/d2_cfg_mgr.cc @@ -358,7 +358,7 @@ D2CfgMgr::buildParams(ConstElementPtr params_config) { // Fetch the parameters in the config, performing any logical // validation required. - asiolink::IOAddress ip_address(0); + asiolink::IOAddress ip_address(0U); uint32_t port = 0; uint32_t dns_server_timeout = 0; dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP; diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am index cc19d70882..26e3d6f93b 100644 --- a/src/bin/dhcp4/Makefile.am +++ b/src/bin/dhcp4/Makefile.am @@ -31,7 +31,7 @@ EXTRA_DIST += dhcp4_parser.yy if GENERATE_DOCS kea-dhcp4.8: kea-dhcp4.xml @XSLTPROC@ --novalid --xinclude --nonet -o $@ \ - http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \ + http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \ $(srcdir)/kea-dhcp4.xml else diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 06d5ee6472..1f74164217 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -124,7 +124,7 @@ class ControlledDhcpv4Srv : public isc::dhcp::Dhcpv4Srv { /// @brief Callback that will be called from iface_mgr when data /// is received over control socket. /// - /// This static callback method is called from IfaceMgr::receive6() method, + /// This static callback method is called from IfaceMgr::receive4() method, /// when there is a new command or configuration sent over control socket /// (that was sent from some yet unspecified sender). static void sessionReader(void); diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 22362d2259..45c33596d3 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -233,7 +233,7 @@ the message. % DHCP4_DHCP4O6_RECEIVING receiving DHCPv4o6 packet from DHCPv6 server This debug message is printed when the server is receiving a DHCPv4o6 -from the DHCPv6 server over inter-process communication socket. +from the DHCPv4 server over inter-process communication socket. % DHCP4_DHCP4O6_RESPONSE_DATA %1: responding with packet %2 (type %3), packet details: %4 A debug message including the detailed data about the packet being diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy index 555cb965f3..e9fbd5b2e1 100644 --- a/src/bin/dhcp4/dhcp4_parser.yy +++ b/src/bin/dhcp4/dhcp4_parser.yy @@ -82,13 +82,13 @@ using namespace std; LFC_INTERVAL "lfc-interval" READONLY "readonly" CONNECT_TIMEOUT "connect-timeout" - CONTACT_POINTS "contact-points" - KEYSPACE "keyspace" - MAX_RECONNECT_TRIES "max-reconnect-tries" + TCP_NODELAY "tcp-nodelay" RECONNECT_WAIT_TIME "reconnect-wait-time" REQUEST_TIMEOUT "request-timeout" TCP_KEEPALIVE "tcp-keepalive" - TCP_NODELAY "tcp-nodelay" + CONTACT_POINTS "contact-points" + KEYSPACE "keyspace" + MAX_RECONNECT_TRIES "max-reconnect-tries" VALID_LIFETIME "valid-lifetime" RENEW_TIMER "renew-timer" @@ -98,6 +98,9 @@ using namespace std; SUBNET_4O6_INTERFACE "4o6-interface" SUBNET_4O6_INTERFACE_ID "4o6-interface-id" SUBNET_4O6_SUBNET "4o6-subnet" + SUBNET_V4_PSID_OFFSET "v4-psid-offset" + SUBNET_V4_PSID_LEN "v4-psid-len" + SUBNET_V4_EXCLUDED_PSIDS "v4-excluded-psids" OPTION_DEF "option-def" OPTION_DATA "option-data" NAME "name" @@ -480,7 +483,6 @@ match_client_id: MATCH_CLIENT_ID COLON BOOLEAN { ctx.stack_.back()->set("match-client-id", match); }; - interfaces_config: INTERFACES_CONFIG { ElementPtr i(new MapElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("interfaces-config", i); @@ -620,12 +622,12 @@ database_map_param: database_type | lfc_interval | readonly | connect_timeout - | contact_points - | max_reconnect_tries + | tcp_nodelay | reconnect_wait_time | request_timeout | tcp_keepalive - | tcp_nodelay + | contact_points + | max_reconnect_tries | keyspace | unknown_map_entry ; @@ -700,6 +702,16 @@ connect_timeout: CONNECT_TIMEOUT COLON INTEGER { ctx.stack_.back()->set("connect-timeout", n); }; +tcp_nodelay: TCP_NODELAY COLON BOOLEAN { + ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("tcp-nodelay", n); +}; + +reconnect_wait_time: RECONNECT_WAIT_TIME COLON INTEGER { + ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("reconnect-wait-time", n); +}; + request_timeout: REQUEST_TIMEOUT COLON INTEGER { ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); ctx.stack_.back()->set("request-timeout", n); @@ -710,11 +722,6 @@ tcp_keepalive: TCP_KEEPALIVE COLON INTEGER { ctx.stack_.back()->set("tcp-keepalive", n); }; -tcp_nodelay: TCP_NODELAY COLON BOOLEAN { - ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); - ctx.stack_.back()->set("tcp-nodelay", n); -}; - contact_points: CONTACT_POINTS { ctx.enter(ctx.NO_KEYWORD); } COLON STRING { @@ -736,11 +743,6 @@ max_reconnect_tries: MAX_RECONNECT_TRIES COLON INTEGER { ctx.stack_.back()->set("max-reconnect-tries", n); }; -reconnect_wait_time: RECONNECT_WAIT_TIME COLON INTEGER { - ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); - ctx.stack_.back()->set("reconnect-wait-time", n); -}; - host_reservation_identifiers: HOST_RESERVATION_IDENTIFIERS { ElementPtr l(new ListElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("host-reservation-identifiers", l); @@ -995,6 +997,9 @@ subnet4_param: valid_lifetime | subnet_4o6_interface | subnet_4o6_interface_id | subnet_4o6_subnet + | subnet_v4_psid_offset + | subnet_v4_psid_len + | subnet_v4_excluded_psids | user_context | comment | unknown_map_entry @@ -1032,6 +1037,28 @@ subnet_4o6_subnet: SUBNET_4O6_SUBNET { ctx.leave(); }; +subnet_v4_psid_offset: SUBNET_V4_PSID_OFFSET COLON INTEGER { + ElementPtr offset(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("v4-psid-offset", offset); +}; + +subnet_v4_psid_len: SUBNET_V4_PSID_LEN COLON INTEGER { + ElementPtr psid_len(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("v4-psid-len", psid_len); +}; + +// This defines the "v4-excluded-psids": [ ... ] entry that may appear +// in subnet4 entries. +subnet_v4_excluded_psids: SUBNET_V4_EXCLUDED_PSIDS { + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("v4-excluded-psids", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.NO_KEYWORD); +} COLON LSQUARE_BRACKET list_content RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + interface: INTERFACE { ctx.enter(ctx.NO_KEYWORD); } COLON STRING { @@ -1918,7 +1945,7 @@ replace_client_name: REPLACE_CLIENT_NAME { replace_client_name_value: WHEN_PRESENT { - $$ = ElementPtr(new StringElement("when-present", ctx.loc2pos(@1))); + $$ = ElementPtr(new StringElement("when-present", ctx.loc2pos(@1))); } | NEVER { $$ = ElementPtr(new StringElement("never", ctx.loc2pos(@1))); diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index dfda5279b6..4f35ee0d5b 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -185,7 +185,7 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine, .arg(query_->getLabel()) .arg(classes.toText()); } -}; +} void Dhcpv4Exchange::initResponse() { @@ -639,6 +639,10 @@ Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop, } } + selector.address_plus_port_ = query->getAddressPlusPort(); + selector.psid_offset_ = query->getPsidOffset(); + selector.psid_len_ = query->getPsidLen(); + CfgMgr& cfgmgr = CfgMgr::instance(); subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector); @@ -984,6 +988,8 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) { callout_handle->getArgument("query4", query); } + updateDhcpV4AddressPlusPortPacket(query); + AllocEngine::ClientContext4Ptr ctx; try { @@ -1169,6 +1175,36 @@ Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle, } } +void Dhcpv4Srv::updateDhcpV4AddressPlusPortPacket(Pkt4Ptr& query) { + OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast + (query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST)); + if (option_prl) { + // PRL option exists, so check if the portparams option code is + // included in it. + const std::vector& + requested_opts = option_prl->getValues(); + if (std::find(requested_opts.begin(), requested_opts.end(), + DHO_V4_PORTPARAMS) != requested_opts.end()) { + // Client has requested DHO_V4_PORTPARAMS via Parameter Request + // List option. + query->setAddressPlusPort(true); + OptionPtr portparams = query->getOption(DHO_V4_PORTPARAMS); + if (portparams) { + OptionCustomPtr oc = boost::dynamic_pointer_cast(portparams); + if (oc) { + // Read offset for address plus port + query->setPsidOffset(PSIDOffset(oc->readInteger(0)).asUint8()); + PSIDTuple psid; + // Read PSID length / PSID value for address plus port + psid = oc->readPsid(1); + query->setPsidLen(psid.first.asUint8()); + query->setPsid(psid.second.asUint16()); + } + } + } + } +} + void Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle, Pkt4Ptr& rsp) { @@ -1864,11 +1900,11 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) { OptionCustom>(query->getOption(DHO_DHCP_REQUESTED_ADDRESS)); IOAddress hint(IOAddress::IPV4_ZERO_ADDRESS()); if (opt_requested_address) { - hint = opt_requested_address->readAddress(); - + hint = IOAddress(opt_requested_address->readAddress().toText(), + query->getPsidOffset(), query->getPsidLen(), query->getPsid()); } else if (!query->getCiaddr().isV4Zero()) { - hint = query->getCiaddr(); - + hint = IOAddress(query->getCiaddr().toText(), + query->getPsidOffset(), query->getPsidLen(), query->getPsid()); } HWAddrPtr hwaddr = query->getHWAddr(); @@ -2180,6 +2216,16 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) { } + if (subnet->get4o6().enabled() && subnet->get4o6().getPsidLen() && + subnet->get4o6().getPsidLen() == lease->addr_.getPsidLen()) { + OptionDefinitionPtr portparams_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_V4_PORTPARAMS); + OptionCustomPtr portparams(new OptionCustom(*portparams_def, Option::V4)); + portparams->writeInteger(lease->addr_.getPsidOffset(), 0); + portparams->writePsid(PSIDLen(lease->addr_.getPsidLen()), PSID(lease->addr_.getPsid()), 1); + resp->addOption(portparams); + } + // Create NameChangeRequests if DDNS is enabled and this is a // real allocation. if (!fake_allocation && CfgMgr::instance().ddnsEnabled()) { diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index c095cdfef0..6e4fbdc19e 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -271,6 +271,11 @@ class Dhcpv4Srv : public Daemon { bool allow_packet_park = true); + /// @brief Update DHCPv4 packet with address plus port information. + /// + /// @param query A pointer to the packet to be updated. + void updateDhcpV4AddressPlusPortPacket(Pkt4Ptr& query); + /// @brief Instructs the server to shut down. void shutdown(); diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index ab61bf3c85..1331dd1298 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -90,7 +90,7 @@ class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv { using Dhcpv4Srv::network_state_; }; -/// @brief Fixture class intended for testin control channel in the DHCPv4Srv +/// @brief Fixture class intended for testing control channel in the DHCPv4Srv class CtrlChannelDhcpv4SrvTest : public ::testing::Test { public: diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index e50ed9fef9..ac637d179f 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -598,8 +598,8 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { return (answer); } } catch (const std::exception& ex) { - return (isc::config::createAnswer(1, "Failed to process configuration:" - + string(ex.what()))); + return (isc::config::createAnswer(1, "Failed to process configuration:" + + string(ex.what()))); } // Re-open lease and host database with new parameters. @@ -610,8 +610,8 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { cfg_db->setAppendedParameters("universe=6"); cfg_db->createManagers(); } catch (const std::exception& ex) { - return (isc::config::createAnswer(1, "Unable to open database: " - + std::string(ex.what()))); + return (isc::config::createAnswer(1, "Unable to open database: " + + std::string(ex.what()))); } // Regenerate server identifier if needed. diff --git a/src/bin/dhcp6/dhcp6_hooks.dox b/src/bin/dhcp6/dhcp6_hooks.dox index 8b76fd5a04..be89c09bea 100644 --- a/src/bin/dhcp6/dhcp6_hooks.dox +++ b/src/bin/dhcp6/dhcp6_hooks.dox @@ -327,7 +327,7 @@ to the end of this list. - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: in/out - @b Description: This callout is executed when server's response - is about to be send back to the client. The sole argument "response6" + is about to be sent back to the client. The sole argument "response6" contains a pointer to an @c isc::dhcp::Pkt6 object that contains the packet, with set source and destination addresses, interface over which it will be send, list of all options and relay information. All fields @@ -350,7 +350,7 @@ to the end of this list. - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: in/out - @b Description: This callout is executed when server's response is - assembled into binary form and is about to be send back to the + assembled into binary form and is about to be sent back to the client. The sole argument "response6" contains a pointer to an @c isc::dhcp::Pkt6 object that contains the packet, with set source and destination addresses, interface over which it will be sent, list of diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy index 32febb744a..f163d2a7dc 100644 --- a/src/bin/dhcp6/dhcp6_parser.yy +++ b/src/bin/dhcp6/dhcp6_parser.yy @@ -70,13 +70,13 @@ using namespace std; LFC_INTERVAL "lfc-interval" READONLY "readonly" CONNECT_TIMEOUT "connect-timeout" - CONTACT_POINTS "contact-points" - MAX_RECONNECT_TRIES "max-reconnect-tries" + TCP_NODELAY "tcp-nodelay" RECONNECT_WAIT_TIME "reconnect-wait-time" - KEYSPACE "keyspace" REQUEST_TIMEOUT "request-timeout" TCP_KEEPALIVE "tcp-keepalive" - TCP_NODELAY "tcp-nodelay" + CONTACT_POINTS "contact-points" + KEYSPACE "keyspace" + MAX_RECONNECT_TRIES "max-reconnect-tries" PREFERRED_LIFETIME "preferred-lifetime" VALID_LIFETIME "valid-lifetime" @@ -522,7 +522,6 @@ re_detect: RE_DETECT COLON BOOLEAN { ctx.stack_.back()->set("re-detect", b); }; - lease_database: LEASE_DATABASE { ElementPtr i(new MapElement(ctx.loc2pos(@1))); ctx.stack_.back()->set("lease-database", i); @@ -589,12 +588,12 @@ database_map_param: database_type | lfc_interval | readonly | connect_timeout - | contact_points - | max_reconnect_tries + | tcp_nodelay | reconnect_wait_time | request_timeout | tcp_keepalive - | tcp_nodelay + | contact_points + | max_reconnect_tries | keyspace | unknown_map_entry ; @@ -669,6 +668,11 @@ connect_timeout: CONNECT_TIMEOUT COLON INTEGER { ctx.stack_.back()->set("connect-timeout", n); }; +tcp_nodelay: TCP_NODELAY COLON BOOLEAN { + ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("tcp-nodelay", n); +}; + reconnect_wait_time: RECONNECT_WAIT_TIME COLON INTEGER { ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); ctx.stack_.back()->set("reconnect-wait-time", n); @@ -684,11 +688,6 @@ tcp_keepalive: TCP_KEEPALIVE COLON INTEGER { ctx.stack_.back()->set("tcp-keepalive", n); }; -tcp_nodelay: TCP_NODELAY COLON BOOLEAN { - ElementPtr n(new BoolElement($3, ctx.loc2pos(@3))); - ctx.stack_.back()->set("tcp-nodelay", n); -}; - contact_points: CONTACT_POINTS { ctx.enter(ctx.NO_KEYWORD); } COLON STRING { @@ -697,11 +696,6 @@ contact_points: CONTACT_POINTS { ctx.leave(); }; -max_reconnect_tries: MAX_RECONNECT_TRIES COLON INTEGER { - ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); - ctx.stack_.back()->set("max-reconnect-tries", n); -}; - keyspace: KEYSPACE { ctx.enter(ctx.NO_KEYWORD); } COLON STRING { @@ -710,6 +704,10 @@ keyspace: KEYSPACE { ctx.leave(); }; +max_reconnect_tries: MAX_RECONNECT_TRIES COLON INTEGER { + ElementPtr n(new IntElement($3, ctx.loc2pos(@3))); + ctx.stack_.back()->set("max-reconnect-tries", n); +}; mac_sources: MAC_SOURCES { ElementPtr l(new ListElement(ctx.loc2pos(@1))); diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 9ed2a3ff70..5b13e8154b 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1454,6 +1454,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, if (answer_opt) { answer->addOption(answer_opt); } + break; } default: break; diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc index 7ffe9fc24d..8f46ff6b83 100644 --- a/src/bin/dhcp6/tests/shared_network_unittest.cc +++ b/src/bin/dhcp6/tests/shared_network_unittest.cc @@ -427,7 +427,7 @@ const char* NETWORKS_CONFIG[] = { " }" " ]," " \"subnet6\": [" - " \{" + " {" " \"subnet\": \"3000::/96\"," " \"id\": 1000," " \"interface\": \"eth0\"," diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc index dae8074066..69bc305012 100644 --- a/src/lib/asiolink/io_address.cc +++ b/src/lib/asiolink/io_address.cc @@ -28,7 +28,19 @@ namespace asiolink { // XXX: we cannot simply construct the address in the initialization list, // because we'd like to throw our own exception on failure. -IOAddress::IOAddress(const std::string& address_str) { +IOAddress::IOAddress(const std::string& address_str) : + offset_(0), psid_len_(0), psid_(0) { + boost::system::error_code err; + asio_address_ = ip::address::from_string(address_str, err); + if (err) { + isc_throw(IOError, "Failed to convert string to address '" + << address_str << "': " << err.message()); + } +} + +IOAddress::IOAddress(const std::string& address_str, uint8_t offset, + uint8_t psid_len, uint16_t psid) : + offset_(offset), psid_len_(psid_len), psid_(psid) { boost::system::error_code err; asio_address_ = ip::address::from_string(address_str, err); if (err) { @@ -38,12 +50,30 @@ IOAddress::IOAddress(const std::string& address_str) { } IOAddress::IOAddress(const boost::asio::ip::address& asio_address) : - asio_address_(asio_address) -{} + asio_address_(asio_address), offset_(0), psid_len_(0), psid_(0) { +} -IOAddress::IOAddress(uint32_t v4address): - asio_address_(boost::asio::ip::address_v4(v4address)) { +IOAddress::IOAddress(uint32_t v4address) : + asio_address_(boost::asio::ip::address_v4(v4address)), + offset_(0), psid_len_(0), psid_(0) { +} +IOAddress::IOAddress(uint32_t v4address, uint8_t offset, uint8_t psid_len, + uint16_t psid) : asio_address_(boost::asio::ip::address_v4(v4address)), + offset_(offset), psid_len_(psid_len), psid_(psid) { +} + +IOAddress::IOAddress(uint64_t v4address) : + asio_address_(boost::asio::ip::address_v4(static_cast(v4address))), + offset_(0), psid_len_(0), psid_(0) { + offset_ = static_cast(v4address >> + (8 * (sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint8_t)))); + psid_len_ = static_cast((v4address << + (8 * sizeof(uint8_t))) >> + (8 * (sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint8_t)))); + psid_ = static_cast((v4address << + (8 * (sizeof(uint8_t) + sizeof(uint8_t)))) >> + (8 * (sizeof(uint32_t) + sizeof(uint16_t)))); } string @@ -117,6 +147,21 @@ IOAddress::toUint32() const { } } +uint64_t +IOAddress::addressPlusPortToUint64() const { + if (asio_address_.is_v4()) { + uint64_t addr = offset_; + addr = (addr << (8 * sizeof(uint8_t))) + psid_len_; + addr = (addr << (8 * sizeof(uint16_t))) + psid_; + addr = (addr << (8 * sizeof(uint32_t))) + + static_cast(asio_address_.to_v4().to_ulong()); + return addr; + } else { + isc_throw(BadValue, "Can't convert " << toText() + << " address to IPv4."); + } +} + std::ostream& operator<<(std::ostream& os, const IOAddress& address) { os << address.toText(); @@ -158,18 +203,26 @@ IOAddress::subtract(const IOAddress& a, const IOAddress& b) { } IOAddress -IOAddress::increase(const IOAddress& addr) { +IOAddress::increase(const IOAddress& addr, uint8_t, uint8_t psid_len) { + uint16_t increment = 0; + if (psid_len) { + increment = ((addr.psid_ + 1) & ((1 << psid_len) - 1)); + } + std::vector packed(addr.toBytes()); - // Start increasing the least significant byte - for (int i = packed.size() - 1; i >= 0; --i) { - // if we haven't overflowed (0xff -> 0x0), than we are done - if (++packed[i] != 0) { - break; + if (!increment) { + // Start increasing the least significant byte + for (int i = packed.size() - 1; i >= 0; --i) { + // if we haven't overflowed (0xff -> 0x0), than we are done + if (++packed[i] != 0) { + break; + } } } - return (IOAddress::fromBytes(addr.getFamily(), &packed[0])); + return IOAddress(IOAddress::fromBytes(addr.getFamily(), &packed[0]).toText(), + 0, 0, increment); } diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h index 42f09d7a9f..4d6c0eeee0 100644 --- a/src/lib/asiolink/io_address.h +++ b/src/lib/asiolink/io_address.h @@ -38,20 +38,20 @@ namespace asiolink { /// separators. static constexpr size_t V6ADDRESS_TEXT_MAX_LEN = 39u; -/// \brief The \c IOAddress class represents an IP addresses (version +/// @brief The \c IOAddress class represents an IP addresses (version /// agnostic) /// /// This class is a wrapper for the ASIO \c ip::address class. class IOAddress { public: /// - /// \name Constructors and Destructor + /// @name Constructors and Destructor /// /// This class is copyable. We use default versions of copy constructor /// and the assignment operator. /// We use the default destructor. //@{ - /// \brief Constructor from string. + /// @brief Constructor from string. /// /// This constructor converts a textual representation of IPv4 and IPv6 /// addresses into an IOAddress object. @@ -60,17 +60,34 @@ class IOAddress { /// This constructor allocates memory for the object, and if that fails /// a corresponding standard exception will be thrown. /// - /// \param address_str Textual representation of address. + /// @param address_str Textual representation of address. IOAddress(const std::string& address_str); - /// \brief Constructor from an ASIO \c ip::address object. + /// @brief Constructor from string. + /// + /// This constructor converts a textual representation of IPv4, IPV4 + /// address plus port and IPv6 + /// addresses into an IOAddress object. + /// If \c address_str is not a valid representation of any type of + /// address, an exception of class \c IOError will be thrown. + /// This constructor allocates memory for the object, and if that fails + /// a corresponding standard exception will be thrown. + /// + /// @param address_str Textual representation of address. + /// @param offset value of offset in IPv4 address plus port. + /// @param psid_len value of psid_len in IPv4 address plus port. + /// @param psid value of psid in IPv4 address plus port. + IOAddress(const std::string& address_str, uint8_t offset, uint8_t psid_len, + uint16_t psid); + + /// @brief Constructor from an ASIO \c ip::address object. /// /// This constructor is intended to be used within the wrapper /// implementation; user applications of the wrapper API won't use it. /// /// This constructor never throws an exception. /// - /// \param asio_address The ASIO \c ip::address to be converted. + /// @param asio_address The ASIO \c ip::address to be converted. IOAddress(const boost::asio::ip::address& asio_address); //@} @@ -83,112 +100,136 @@ class IOAddress { /// @param v4address IPv4 address represented by uint32_t IOAddress(uint32_t v4address); - /// \brief Convert the address to a string. + /// @brief Constructor for ip::address_v4 address plus port object. + /// + /// This constructor is intended to be used when constructing + /// IPv4 address plus port out of uint32_t type, uint8_t offset, + /// uint8_t psid_len and uint16_t psid. Passed value for address + /// must be in network byte order + /// + /// @param v4address IPv4 address represented by uint32_t + /// @param offset PSID offset (see https://tools.ietf.org/html/rfc7618) + /// @param psid_len PSID length + /// @param psid PSID value + IOAddress(uint32_t v4address, + uint8_t offset, + uint8_t psid_len, + uint16_t psid); + + /// @brief Constructor for ip::address_v4 address plus port object. + /// + /// This constructor is intended to be used when constructing + /// IPv4 address plus port out of uint64_t type. + /// + /// @param v4address IPv4 address plus port represented by uint64_t + IOAddress(uint64_t v4address); + + /// @brief Convert the address to a string. /// /// This method is basically expected to be exception free, but /// generating the string will involve resource allocation, /// and if it fails the corresponding standard exception will be thrown. /// - /// \return A string representation of the address. + /// @return A string representation of the address. std::string toText() const; - /// \brief Returns the address family + /// @brief Returns the address family /// - /// \return AF_INET for IPv4 or AF_INET6 for IPv6. + /// @return AF_INET for IPv4 or AF_INET6 for IPv6. short getFamily() const; - /// \brief Convenience function to check for an IPv4 address + /// @brief Convenience function to check for an IPv4 address /// - /// \return true if the address is a V4 address + /// @return true if the address is a V4 address bool isV4() const { return (asio_address_.is_v4()); } - /// \brief Convenience function to check if it is an IPv4 zero address. + /// @brief Convenience function to check if it is an IPv4 zero address. /// - /// \return true if the address is the zero IPv4 address. + /// @return true if the address is the zero IPv4 address. bool isV4Zero() const { return (equals(IPV4_ZERO_ADDRESS())); } - /// \brief Convenience function to check if it is an IPv4 broadcast + /// @brief Convenience function to check if it is an IPv4 broadcast /// address. /// - /// \return true if the address is the broadcast IPv4 address. + /// @return true if the address is the broadcast IPv4 address. bool isV4Bcast() const { return (equals(IPV4_BCAST_ADDRESS())); } - /// \brief Convenience function to check for an IPv6 address + /// @brief Convenience function to check for an IPv6 address /// - /// \return true if the address is a V6 address + /// @return true if the address is a V6 address bool isV6() const { return (asio_address_.is_v6()); } - /// \brief Convenience function to check if it is an IPv4 zero address. + /// @brief Convenience function to check if it is an IPv4 zero address. /// - /// \return true if the address is the zero IPv4 address. + /// @return true if the address is the zero IPv4 address. bool isV6Zero() const { return (equals(IPV6_ZERO_ADDRESS())); } - /// \brief checks whether and address is IPv6 and is link-local + /// @brief checks whether and address is IPv6 and is link-local /// - /// \return true if the address is IPv6 link-local, false otherwise + /// @return true if the address is IPv6 link-local, false otherwise bool isV6LinkLocal() const; - /// \brief checks whether and address is IPv6 and is multicast + /// @brief checks whether and address is IPv6 and is multicast /// - /// \return true if the address is IPv6 multicast, false otherwise + /// @return true if the address is IPv6 multicast, false otherwise bool isV6Multicast() const; - /// \brief Creates an address from over wire data. + /// @brief Creates an address from over wire data. /// /// \param family AF_INET for IPv4 or AF_INET6 for IPv6. /// \param data pointer to first char of data /// - /// \return Created IOAddress object + /// @return Created IOAddress object static IOAddress fromBytes(short family, const uint8_t* data); - /// \brief Return address as set of bytes + /// @brief Return address as set of bytes /// - /// \return Contents of the address as a set of bytes in network-byte + /// @return Contents of the address as a set of bytes in network-byte /// order. std::vector toBytes() const; - /// \brief Compare addresses for equality + /// @brief Compare addresses for equality /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return true if addresses are equal, false if not. + /// @return true if addresses are equal, false if not. bool equals(const IOAddress& other) const { return (asio_address_ == other.asio_address_); } - /// \brief Compare addresses for equality + /// @brief Compare addresses for equality /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return true if addresses are equal, false if not. + /// @return true if addresses are equal, false if not. bool operator==(const IOAddress& other) const { return equals(other); } - /// \brief Compare addresses for inequality + /// @brief Compare addresses for inequality /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return false if addresses are equal, true if not. + /// @return false if addresses are equal, true if not. bool nequals(const IOAddress& other) const { return (!equals(other)); } - /// \brief Checks if one address is smaller than the other + /// @brief Checks if one address is smaller than the other /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return true if this address is smaller than the other address. + /// @return true if this address is smaller than the other address. /// /// It is useful for comparing which address is bigger. /// Operations within one protocol family are obvious. @@ -205,11 +246,11 @@ class IOAddress { return (this->getFamily() < other.getFamily()); } - /// \brief Checks if one address is smaller or equal than the other + /// @brief Checks if one address is smaller or equal than the other /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return true if this address is smaller than the other address. + /// @return true if this address is smaller than the other address. bool smallerEqual(const IOAddress& other) const { if (equals(other)) { return (true); @@ -217,29 +258,29 @@ class IOAddress { return (lessThan(other)); } - /// \brief Checks if one address is smaller than the other + /// @brief Checks if one address is smaller than the other /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// See \ref lessThan method for details. + /// See @ref lessThan method for details. bool operator<(const IOAddress& other) const { return (lessThan(other)); } - /// \brief Checks if one address is smaller or equal than the other + /// @brief Checks if one address is smaller or equal than the other /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// See \ref smallerEqual method for details. + /// See @ref smallerEqual method for details. bool operator<=(const IOAddress& other) const { return (smallerEqual(other)); } - /// \brief Compare addresses for inequality + /// @brief Compare addresses for inequality /// - /// \param other Address to compare against. + /// @param other Address to compare against. /// - /// \return false if addresses are equal, true if not. + /// @return false if addresses are equal, true if not. bool operator!=(const IOAddress& other) const { return (nequals(other)); } @@ -282,32 +323,43 @@ class IOAddress { /// could take extra parameter that specifies the value by which the /// address should be increased. /// - /// @param addr address to be increased + /// @param addr address being increased + /// @param offset PSID offset + /// @param psid_len PSID length /// @return address increased by one static IOAddress - increase(const IOAddress& addr); + increase(const IOAddress& addr, uint8_t offset = 0, uint8_t psid_len = 0); - /// \brief Converts IPv4 address to uint32_t + /// @brief Converts IPv4 address to uint32_t /// /// Will throw BadValue exception if that is not IPv4 /// address. /// - /// \return uint32_t that represents IPv4 address in + /// @return uint32_t that represents IPv4 address in /// network byte order uint32_t toUint32() const; + /// @brief Converts IPv4 address plus port to uint64_t + /// + /// Will throw BadValue exception if that is not IPv4 + /// address. + /// + /// @return uint64_t that represents IPv4 address plus port in + /// network byte order + uint64_t addressPlusPortToUint64() const; + /// @name Methods returning @c IOAddress objects encapsulating typical addresses. /// //@{ /// @brief Returns an address set to all zeros. static const IOAddress& IPV4_ZERO_ADDRESS() { - static IOAddress address(0); + static IOAddress address(0U); return (address); } /// @brief Returns a "255.255.255.255" broadcast address. static const IOAddress& IPV4_BCAST_ADDRESS() { - static IOAddress address(0xFFFFFFFF); + static IOAddress address(0xFFFFFFFFU); return (address); } @@ -317,13 +369,28 @@ class IOAddress { return (address); } + uint8_t getPsidOffset() const { + return offset_; + } + + uint8_t getPsidLen() const { + return psid_len_; + } + + uint16_t getPsid() const { + return psid_; + } + //@} private: boost::asio::ip::address asio_address_; + uint8_t offset_; + uint8_t psid_len_; + uint16_t psid_; }; -/// \brief Insert the IOAddress as a string into stream. +/// @brief Insert the IOAddress as a string into stream. /// /// This method converts the \c address into a string and inserts it /// into the output stream \c os. @@ -331,10 +398,10 @@ class IOAddress { /// This function overloads the global operator<< to behave as described /// in ostream::operator<< but applied to \c IOAddress objects. /// -/// \param os A \c std::ostream object on which the insertion operation is +/// @param os A \c std::ostream object on which the insertion operation is /// performed. -/// \param address The \c IOAddress object output by the operation. -/// \return A reference to the same \c std::ostream object referenced by +/// @param address The \c IOAddress object output by the operation. +/// @return A reference to the same \c std::ostream object referenced by /// parameter \c os after the insertion operation. std::ostream& operator<<(std::ostream& os, const IOAddress& address); diff --git a/src/lib/dhcp/dhcp4.h b/src/lib/dhcp/dhcp4.h index b72af11bac..af62ef93ed 100644 --- a/src/lib/dhcp/dhcp4.h +++ b/src/lib/dhcp/dhcp4.h @@ -210,7 +210,7 @@ enum DHCPOptionType { DHO_V4_CAPTIVE_PORTAL = 160, // 161-209 are removed/unassigned // DHO_PATH_PREFIX = 210, -// DHO_REBOOT_TIME = 211, +// DHO_REBOOT_TIME = 211, DHO_6RD = 212, DHO_V4_ACCESS_DOMAIN = 213, // 214-219 are removed/unassigned diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index ac487f085d..f7c32fa74f 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -113,7 +113,7 @@ 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" + isc_throw(BadDataTypeCast, "PSID value can be specified only for" " an option comprising an array of PSID length / value" " tuples"); } diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index 358dcc0728..88a677e69d 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -197,6 +197,51 @@ struct OptionDataTypeTraits { static const OptionDataType type = OPT_STRING_TYPE; }; +/// @brief Encapsulates PSID offset. +class PSIDOffset { +public: + + /// @brief Default constructor. + PSIDOffset() : psid_offset_(0) { } + + /// @brief Constructor. + /// + /// It checks that the specified value is not greater than + /// 16, which is a maximum value for the PSID offset. + /// + /// @param psid_offset PSID offset. + /// @throw isc::OutOfRange If specified PSID offset is greater than 16. + explicit PSIDOffset(const uint8_t psid_offset) + : psid_offset_(psid_offset) { + if (psid_offset_ > sizeof(uint16_t) * 8) { + isc_throw(isc::OutOfRange, "invalid value " + << asUnsigned() << " of PSID offset"); + } + } + + /// @brief Returns PSID offset as uint8_t value. + uint8_t asUint8() const { + return (psid_offset_); + } + + /// @brief Returns PSID offset 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 logging an uint8_t value prints a character rather + /// than a number. + unsigned int asUnsigned() const { + return (static_cast(psid_offset_)); + } + +private: + + /// @brief PSID offset. + uint8_t psid_offset_; +}; + /// @brief Encapsulates PSID length. class PSIDLen { public: diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc index 4f77621a33..9bb6d9c7a0 100644 --- a/src/lib/dhcp/option_definition.cc +++ b/src/lib/dhcp/option_definition.cc @@ -662,46 +662,46 @@ OptionDefinition::writeToBuffer(Option::Universe u, OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf); return; - } + } case OPT_PSID_TYPE: - { - std::string txt = value; + { + std::string txt = value; - // first let's remove any whitespaces - boost::erase_all(txt, " "); // space - boost::erase_all(txt, "\t"); // tabulation + // 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("/"); + // Is this prefix/len notation? + size_t pos = txt.find("/"); - if (pos == string::npos) { - isc_throw(BadDataTypeCast, "provided PSID value " - << value << " is not valid"); - } + 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); + 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; + 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 = 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"); - } + 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; - } + OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf); + return; + } case OPT_STRING_TYPE: OptionDataTypeUtil::writeString(value, buf); return; @@ -709,12 +709,12 @@ OptionDefinition::writeToBuffer(Option::Universe u, OptionDataTypeUtil::writeFqdn(value, buf); return; case OPT_TUPLE_TYPE: - { - OpaqueDataTuple::LengthFieldType lft = u == Option::V4 ? - OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; - OptionDataTypeUtil::writeTuple(value, lft, buf); - return; - } + { + OpaqueDataTuple::LengthFieldType lft = u == Option::V4 ? + OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES; + OptionDataTypeUtil::writeTuple(value, lft, buf); + return; + } default: // We hit this point because invalid option data type has been specified // This may be the case because 'empty' or 'record' data type has been diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc index 007e3f91fc..ebd7a510ab 100644 --- a/src/lib/dhcp/pkt4.cc +++ b/src/lib/dhcp/pkt4.cc @@ -40,7 +40,11 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid) ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), - giaddr_(DEFAULT_ADDRESS) + giaddr_(DEFAULT_ADDRESS), + address_plus_port_(false), + psid_offset_(0), + psid_len_(0), + psid_(0) { memset(sname_, 0, MAX_SNAME_LEN); memset(file_, 0, MAX_FILE_LEN); @@ -59,7 +63,11 @@ Pkt4::Pkt4(const uint8_t* data, size_t len) ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), - giaddr_(DEFAULT_ADDRESS) + giaddr_(DEFAULT_ADDRESS), + address_plus_port_(false), + psid_offset_(0), + psid_len_(0), + psid_(0) { if (len < DHCPV4_PKT_HDR_LEN) { diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h index 26ded314f1..a4312db83b 100644 --- a/src/lib/dhcp/pkt4.h +++ b/src/lib/dhcp/pkt4.h @@ -397,6 +397,46 @@ class Pkt4 : public Pkt { return (false); } + /// @brief Sets address plus port field. + /// + /// @param address_plus_port address plus port value to be set + void setAddressPlusPort(bool address_plus_port) { address_plus_port_ = address_plus_port; }; + + /// @brief Returns address plus port field. + /// + /// @return address plus port field + bool getAddressPlusPort() const { return (address_plus_port_); }; + + /// @brief Sets PSID offset. + /// + /// @param psid_offset PSID offset value to be set + void setPsidOffset(uint8_t psid_offset) { psid_offset_ = psid_offset; }; + + /// @brief Returns PSID offset. + /// + /// @return PSID offset field + uint8_t getPsidOffset() const { return (psid_offset_); }; + + /// @brief Sets PSID len. + /// + /// @param psid_len PSID len value to be set + void setPsidLen(uint8_t psid_len) { psid_len_ = psid_len; }; + + /// @brief Returns PSID len. + /// + /// @return PSID len field + uint8_t getPsidLen() const { return (psid_len_); }; + + /// @brief Sets PSID. + /// + /// @param psid PSID value to be set + void setPsid(uint16_t psid) { psid_ = psid; }; + + /// @brief Returns PSID. + /// + /// @return PSID field + uint16_t getPsid() const { return (psid_); }; + private: /// @brief Generic method that validates and sets HW address. @@ -539,6 +579,18 @@ class Pkt4 : public Pkt { /// file field (128 bytes) uint8_t file_[MAX_FILE_LEN]; + /// @brief Specifies if the packet contains address plus port information + bool address_plus_port_; + + /// @brief Specifies the packet offset for address plus port + uint8_t psid_offset_; + + /// @brief Specifies the packet psid-len for address plus port + uint8_t psid_len_; + + /// @brief Specifies the packet psid for address plus port + uint16_t psid_; + // end of real DHCPv4 fields }; // Pkt4 class diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h index 4e9d82923b..5800496edb 100644 --- a/src/lib/dhcp/std_option_defs.h +++ b/src/lib/dhcp/std_option_defs.h @@ -268,7 +268,8 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = { /// Number of option definitions defined. const int STANDARD_V4_OPTION_DEFINITIONS_SIZE = - sizeof(STANDARD_V4_OPTION_DEFINITIONS) / sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]); + sizeof(STANDARD_V4_OPTION_DEFINITIONS) / + sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]); /// Last resort definitions (only option 43 for now, these definitions /// are applied in deferred unpacking when none is found). @@ -277,7 +278,9 @@ const OptionDefParams LAST_RESORT_V4_OPTION_DEFINITIONS[] = { OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "vendor-encapsulated-options-space" } }; -const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE = 1; +const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE = + sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS) / + sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS[0]); /// Start Definition of DHCPv6 options @@ -421,7 +424,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = { { "v6-access-domain", D6O_V6_ACCESS_DOMAIN, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" }, { "sip-ua-cs-list", D6O_SIP_UA_CS_LIST, OPT_FQDN_TYPE, true, - NO_RECORD_DEF, "" }, + NO_RECORD_DEF, "" }, { "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_TUPLE_TYPE, true, NO_RECORD_DEF, "" }, { "client-arch-type", D6O_CLIENT_ARCH_TYPE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" }, diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc index e0311e7c69..71ef0a3dce 100644 --- a/src/lib/dhcp/tests/option_custom_unittest.cc +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -1181,7 +1181,7 @@ TEST_F(OptionCustomTest, recordDataWithSuboption) { ASSERT_NO_THROW(value0 = option->readInteger(0)); EXPECT_EQ(0x01020304, value0); - IOAddress value1 = 0; + IOAddress value1 = 0U; ASSERT_NO_THROW(value1 = option->readAddress(1)); EXPECT_EQ("192.168.0.1", value1.toText()); diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 391915b37d..39b9d2a98c 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -147,9 +147,11 @@ AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& isc::asiolink::IOAddress AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& address, bool prefix, - const uint8_t prefix_len) { + const uint8_t prefix_len, + uint8_t psid_offset, + uint8_t psid_len) { if (!prefix) { - return (IOAddress::increase(address)); + return (IOAddress::increase(address, psid_offset, psid_len)); } else { return (increasePrefix(address, prefix_len)); } @@ -158,8 +160,29 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& isc::asiolink::IOAddress AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet, const ClientClasses& client_classes, - const DuidPtr&, - const IOAddress&) { + const DuidPtr& duid, + const IOAddress& hint) { + uint8_t psid_offset = 0; + uint8_t psid_len = 0; + if (subnet->get().first.isV4()) { + Subnet4Ptr subnet4 = boost::dynamic_pointer_cast(subnet); + if (subnet4->get4o6().enabled()) { + psid_offset = subnet4->get4o6().getPsidOffset(); + psid_len = subnet4->get4o6().getPsidLen(); + } + } + const IOAddress& result = pickAddressInternal(subnet, client_classes, duid, hint, + psid_offset, psid_len); + return IOAddress(result.toText(), psid_offset, psid_len, result.getPsid()); +} + +isc::asiolink::IOAddress +AllocEngine::IterativeAllocator::pickAddressInternal(const SubnetPtr& subnet, + const ClientClasses& client_classes, + const DuidPtr&, + const IOAddress&, + uint8_t psid_offset, + uint8_t psid_len) { // Is this prefix allocation? bool prefix = pool_type_ == Lease::TYPE_PD; @@ -252,7 +275,7 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet, prefix_len = pool6->getLength(); } - IOAddress next = increaseAddress(last, prefix, prefix_len); + IOAddress next = increaseAddress(last, prefix, prefix_len, psid_offset, psid_len); if ((*it)->inRange(next)) { // the next one is in the pool as well, so we haven't hit // pool boundary yet @@ -2504,7 +2527,6 @@ void AllocEngine::reclaimLeaseInDatabase(const LeasePtrType& lease, // expired-reclaimed state or simply remove it. if (remove_lease) { lease_mgr.deleteLease(lease->addr_); - } else if (!lease_update_fun.empty()) { // Clear FQDN information as we have already sent the // name change request to remove the DNS record. @@ -2912,6 +2934,10 @@ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) { Lease4Ptr client_lease; findClientLease(ctx, client_lease); + if (client_lease && client_lease->addr_.getPsidLen() != ctx.requested_address_.getPsidLen()) { + client_lease = Lease4Ptr(); + } + // new_lease will hold the pointer to the lease that we will offer to the // caller. Lease4Ptr new_lease; @@ -3019,6 +3045,10 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) { Lease4Ptr client_lease; findClientLease(ctx, client_lease); + if (client_lease && client_lease->addr_.getPsidLen() != ctx.requested_address_.getPsidLen()) { + client_lease = Lease4Ptr(); + } + // Obtain the sole instance of the LeaseMgr. LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); @@ -3543,9 +3573,9 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { client_id = ctx.clientid_; } - uint64_t possible_attempts = - subnet->getPoolCapacity(Lease::TYPE_V4, - ctx.query_->getClasses()); + uint64_t possible_attempts = (1 << subnet->get4o6().getPsidLen()) * + subnet->getPoolCapacity(Lease::TYPE_V4, + ctx.query_->getClasses()); uint64_t max_attempts = (attempts_ > 0 ? attempts_ : possible_attempts); // Skip trying if there is no chance to get something if (possible_attempts == 0) { diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 4b1dce5c73..44fd907e21 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -140,6 +140,14 @@ class AllocEngine : public boost::noncopyable { const ClientClasses& client_classes, const DuidPtr& duid, const isc::asiolink::IOAddress& hint); + + virtual isc::asiolink::IOAddress + pickAddressInternal(const SubnetPtr& subnet, + const ClientClasses& client_classes, + const DuidPtr& duid, + const isc::asiolink::IOAddress& hint, + uint8_t psid_offset, + uint8_t psid_len); protected: /// @brief Returns the next prefix @@ -149,8 +157,8 @@ class AllocEngine : public boost::noncopyable { /// increased by prefix length /32 will become 2001:db9::. This method /// is used to iterate over IPv6 prefix pools /// - /// @param prefix prefix to be increased - /// @param prefix_len length of the prefix to be increased + /// @param prefix prefix being increased + /// @param prefix_len length of the prefix being increased /// @return result prefix static isc::asiolink::IOAddress increasePrefix(const isc::asiolink::IOAddress& prefix, @@ -167,7 +175,8 @@ class AllocEngine : public boost::noncopyable { /// @return result address or prefix static isc::asiolink::IOAddress increaseAddress(const isc::asiolink::IOAddress& address, - bool prefix, const uint8_t prefix_len); + bool prefix, const uint8_t prefix_len, + uint8_t psid_offset = 0, uint8_t psid_len = 0); }; diff --git a/src/lib/dhcpsrv/cfg_4o6.h b/src/lib/dhcpsrv/cfg_4o6.h index e231e775e6..cbede74239 100644 --- a/src/lib/dhcpsrv/cfg_4o6.h +++ b/src/lib/dhcpsrv/cfg_4o6.h @@ -8,6 +8,7 @@ #define CFG_4OVER6_H #include +#include #include #include @@ -79,6 +80,30 @@ struct Cfg4o6 : public isc::data::CfgToElement { enabled_ = true; } + /// @brief Returns the offset for address plus port. + /// @return the offset for address plus port + uint8_t getPsidOffset() const { + return (psid_offset_.asUint8()); + } + + /// @brief Sets the offset for address plus port + /// @param offset the offset for address plus port + void setPsidOffset(const uint8_t offset) { + psid_offset_ = PSIDOffset(offset); + } + + /// @brief Returns the psid-len for address plus port. + /// @return the psid-len for address plus port + uint8_t getPsidLen() const { + return (psid_len_.asUint8()); + } + + /// @brief Sets the psid-len for address plus port + /// @param psid_len the psid-len for address plus port + void setPsidLen(const uint8_t psid_len) { + psid_len_ = PSIDLen(psid_len); + } + /// @brief Unparse a configuration object /// /// @return a pointer to unparsed configuration @@ -97,6 +122,12 @@ struct Cfg4o6 : public isc::data::CfgToElement { /// Specifies the v6 interface-id used for v4 subnet selection. OptionPtr interface_id_; + + /// Specifies offset. + PSIDOffset psid_offset_; + + /// Specifies PSID len. + PSIDLen psid_len_; }; } // end of isc::dhcp namespace diff --git a/src/lib/dhcpsrv/cfg_db_access.cc b/src/lib/dhcpsrv/cfg_db_access.cc index 77bc826b12..cdcd7dbc19 100644 --- a/src/lib/dhcpsrv/cfg_db_access.cc +++ b/src/lib/dhcpsrv/cfg_db_access.cc @@ -69,7 +69,7 @@ CfgDbAccess::createManagers() const { HostMgr::checkCacheBackend(true); } -std::string +std::string CfgDbAccess::getAccessString(const std::string& access_string) const { std::ostringstream s; s << access_string; @@ -100,6 +100,9 @@ CfgDbAccess::toElementDbAccessString(const std::string& dbaccess) { std::string value = token.substr(pos + 1); if ((keyword == "lfc-interval") || (keyword == "connect-timeout") || + (keyword == "reconnect-wait-time") || + (keyword == "request-timeout") || + (keyword == "tcp-keepalive") || (keyword == "port")) { // integer parameters int64_t int_value; @@ -112,6 +115,7 @@ CfgDbAccess::toElementDbAccessString(const std::string& dbaccess) { << keyword << "=" << value); } } else if ((keyword == "persist") || + (keyword == "tcp-nodelay") || (keyword == "readonly")) { if (value == "true") { result->set(keyword, Element::create(true)); diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc index 4d12e83e08..f408ad9887 100644 --- a/src/lib/dhcpsrv/cfg_subnets4.cc +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -136,6 +136,22 @@ CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const { continue; // No? Let's try the next one. } + if (selector.address_plus_port_ && + cfg4o6.getPsidOffset() == selector.psid_offset_ && + cfg4o6.getPsidLen() == selector.psid_len_) { + return (*subnet); + } + + if (selector.address_plus_port_ && + cfg4o6.getPsidLen() && !selector.psid_len_) { + return (*subnet); + } + + if (!selector.address_plus_port_ && + (cfg4o6.getPsidOffset() || cfg4o6.getPsidLen())) { + continue; + } + // First match criteria: check if we have a prefix/len defined. std::pair pref = cfg4o6.getSubnet4o6(); if (!pref.first.isV6Zero()) { diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc index 2896b8bc4a..625d249a73 100644 --- a/src/lib/dhcpsrv/cfgmgr.cc +++ b/src/lib/dhcpsrv/cfgmgr.cc @@ -172,6 +172,10 @@ CfgMgr::CfgMgr() // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am // Note: the definition of DHCP_DATA_DIR needs to include quotation marks // See AM_CPPFLAGS definition in Makefile.am + const char* const env = getenv("KEA_DATA_DIR"); + if (env) { + datadir_ = env; + } } CfgMgr::~CfgMgr() { diff --git a/src/lib/dhcpsrv/cql_connection.cc b/src/lib/dhcpsrv/cql_connection.cc index 3b18f02a5b..d39e94a88a 100644 --- a/src/lib/dhcpsrv/cql_connection.cc +++ b/src/lib/dhcpsrv/cql_connection.cc @@ -177,6 +177,8 @@ CqlConnection::openDatabase() { try { port_number = boost::lexical_cast(port); if (port_number < 1 || port_number > 65535) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): " "port outside of range, expected " @@ -184,6 +186,8 @@ CqlConnection::openDatabase() { << port); } } catch (const boost::bad_lexical_cast& ex) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): invalid " "port, expected castable to int, instead got " @@ -199,12 +203,16 @@ CqlConnection::openDatabase() { reconnect_wait_time_number = boost::lexical_cast(reconnect_wait_time); if (reconnect_wait_time_number < 0) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): invalid reconnect " "wait time, expected positive number, instead got " << reconnect_wait_time); } } catch (const boost::bad_lexical_cast& ex) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): " "invalid reconnect wait time, expected " @@ -221,6 +229,8 @@ CqlConnection::openDatabase() { connect_timeout_number = boost::lexical_cast(connect_timeout); if (connect_timeout_number < 0) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): " "invalid connect timeout, expected " @@ -228,6 +238,8 @@ CqlConnection::openDatabase() { << connect_timeout); } } catch (const boost::bad_lexical_cast& ex) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): invalid connect timeout, " "expected castable to int, instead got \"" @@ -242,6 +254,8 @@ CqlConnection::openDatabase() { request_timeout_number = boost::lexical_cast(request_timeout); if (request_timeout_number < 0) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): " "invalid request timeout, expected " @@ -249,6 +263,8 @@ CqlConnection::openDatabase() { << request_timeout); } } catch (const boost::bad_lexical_cast& ex) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): invalid request timeout, " "expected castable to int, instead got \"" @@ -262,6 +278,8 @@ CqlConnection::openDatabase() { try { tcp_keepalive_number = boost::lexical_cast(tcp_keepalive); if (tcp_keepalive_number < 0) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): " "invalid TCP keepalive, expected " @@ -269,6 +287,8 @@ CqlConnection::openDatabase() { << tcp_keepalive); } } catch (const boost::bad_lexical_cast& ex) { + cass_cluster_free(cluster_); + cluster_ = NULL; isc_throw(DbOperationError, "CqlConnection::openDatabase(): invalid TCP keepalive, " "expected castable to int, instead got \"" diff --git a/src/lib/dhcpsrv/cql_exchange.cc b/src/lib/dhcpsrv/cql_exchange.cc index c84d7b72f4..40ad9f9b23 100644 --- a/src/lib/dhcpsrv/cql_exchange.cc +++ b/src/lib/dhcpsrv/cql_exchange.cc @@ -39,14 +39,6 @@ namespace isc { namespace dhcp { -/// @brief Macro to return directly from caller function -#define KEA_CASS_CHECK(cass_error) \ - { \ - if (cass_error != CASS_OK) { \ - return cass_error; \ - } \ - } - /// @brief a helper structure with a function call operator that returns /// key value in a format expected by std::hash. struct ExchangeDataTypeHash { @@ -94,68 +86,7 @@ static AnyTypeMap ANY_TYPE_MAP = { {typeid(cass_int64_t*), EXCHANGE_DATA_TYPE_INT64}, {typeid(std::string*), EXCHANGE_DATA_TYPE_STRING}, {typeid(CassBlob*), EXCHANGE_DATA_TYPE_BYTES}, - {typeid(CassUuid*), EXCHANGE_DATA_TYPE_UUID}, - {typeid(Udt*), EXCHANGE_DATA_TYPE_UDT}, // user data type - {typeid(AnyCollection*), EXCHANGE_DATA_TYPE_COLLECTION}}; - -/// @brief Maps Cassandra type to exchange type -static CassTypeMap CASS_TYPE_MAP = { - {CASS_VALUE_TYPE_CUSTOM, EXCHANGE_DATA_TYPE_UDT}, - {CASS_VALUE_TYPE_ASCII, EXCHANGE_DATA_TYPE_STRING}, - {CASS_VALUE_TYPE_BIGINT, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_BLOB, EXCHANGE_DATA_TYPE_BYTES}, - {CASS_VALUE_TYPE_BOOLEAN, EXCHANGE_DATA_TYPE_BOOL}, - {CASS_VALUE_TYPE_COUNTER, EXCHANGE_DATA_TYPE_INT32}, - {CASS_VALUE_TYPE_DECIMAL, EXCHANGE_DATA_TYPE_INT32}, - {CASS_VALUE_TYPE_DOUBLE, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_FLOAT, EXCHANGE_DATA_TYPE_INT32}, - {CASS_VALUE_TYPE_INT, EXCHANGE_DATA_TYPE_INT32}, - {CASS_VALUE_TYPE_TEXT, EXCHANGE_DATA_TYPE_STRING}, - {CASS_VALUE_TYPE_TIMESTAMP, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_UUID, EXCHANGE_DATA_TYPE_UUID}, - {CASS_VALUE_TYPE_VARCHAR, EXCHANGE_DATA_TYPE_STRING}, - {CASS_VALUE_TYPE_VARINT, EXCHANGE_DATA_TYPE_INT32}, - {CASS_VALUE_TYPE_TIMEUUID, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_INET, EXCHANGE_DATA_TYPE_NONE}, - {CASS_VALUE_TYPE_DATE, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_TIME, EXCHANGE_DATA_TYPE_INT64}, - {CASS_VALUE_TYPE_SMALL_INT, EXCHANGE_DATA_TYPE_INT16}, - {CASS_VALUE_TYPE_TINY_INT, EXCHANGE_DATA_TYPE_INT8}, - {CASS_VALUE_TYPE_LIST, EXCHANGE_DATA_TYPE_COLLECTION}, - {CASS_VALUE_TYPE_MAP, EXCHANGE_DATA_TYPE_COLLECTION}, - {CASS_VALUE_TYPE_SET, EXCHANGE_DATA_TYPE_COLLECTION}, - {CASS_VALUE_TYPE_UDT, EXCHANGE_DATA_TYPE_UDT}, - {CASS_VALUE_TYPE_TUPLE, EXCHANGE_DATA_TYPE_UDT}}; - -/// @brief Udt (user data type) method implementations -/// @{ -Udt::Udt(const CqlConnection& connection, const std::string& name) - : AnyArray(), connection_(connection), name_(name) { - // Create type. - cass_data_type_ = cass_keyspace_meta_user_type_by_name( - connection_.keyspace_meta_, name_.c_str()); - if (!cass_data_type_) { - isc_throw(DbOperationError, - "Udt::Udt(): UDT " << name_ << " does not exist "); - } - // Create container. - cass_user_type_ = cass_user_type_new_from_data_type(cass_data_type_); - if (!cass_user_type_) { - isc_throw(DbOperationError, - "Udt::Udt(): Type " << name_ - << " is not a UDT as expected. "); - } -} - -Udt::~Udt() { - /// @todo: Need to get back to this issue. This is likely a memory leak. - // - // Bug: it seems that if there is no call to - // cass_user_type_set_*(cass_user_type_), then - // cass_user_type_free(cass_user_type_) might SIGSEGV, so we never - // free. Udt objects should have application scope though. - // cass_user_type_free(cass_user_type_); -} + {typeid(CassUuid*), EXCHANGE_DATA_TYPE_UUID}}; /// @} /// @brief AnyArray method implementations @@ -251,238 +182,8 @@ CqlBindUuid(const boost::any& value, return cass_statement_bind_uuid(statement, index, *boost::any_cast(value)); } - -static CassError -CqlBindUdt(const boost::any& value, - const size_t& index, - CassStatement* statement) { - Udt* udt = boost::any_cast(value); - - if (!udt) { - isc_throw(BadValue, "Invalid value specified, not an Udt object"); - } - - size_t i = 0u; - - // Let's iterate over all elements in udt and check that we indeed - // can assign the set function for each specified type. - for (boost::any& element : *udt) { - try { - KEA_CASS_CHECK( - CQL_FUNCTIONS[exchangeType(element)].cqlUdtSetFunction_( - element, i, udt->cass_user_type_)); - } catch (const boost::bad_any_cast& exception) { - isc_throw(DbOperationError, - "CqlCommon::udtSetData(): " - << exception.what() << " when binding parameter " - << " of type " << element.type().name() - << "in UDT with function CQL_FUNCTIONS[" - << exchangeType(element) << "].cqlUdtSetFunction_"); - } - ++i; - } - - return cass_statement_bind_user_type(statement, index, - udt->cass_user_type_); -} - -static CassError -CqlBindCollection(const boost::any& value, - const size_t& index, - CassStatement* statement) { - AnyCollection* elements = boost::any_cast(value); - - CassCollection* collection = - cass_collection_new(CASS_COLLECTION_TYPE_SET, elements->size()); - - // Iterate over all elements and assign appropriate append function - // for each. - for (boost::any& element : *elements) { - ExchangeDataType type = exchangeType(element); - KEA_CASS_CHECK(CQL_FUNCTIONS[type].cqlCollectionAppendFunction_( - element, collection)); - } - - const CassError cass_error = - cass_statement_bind_collection(statement, index, collection); - cass_collection_free(collection); - - return cass_error; -} /// @} -/// @name CqlUdtSet functions for binding data into Cassandra format for -/// insertion of a UDT: -/// @{ -static CassError -CqlUdtSetNone(const boost::any& /* udt_member */, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_null(cass_user_type, position); -} - -static CassError -CqlUdtSetBool(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_bool(cass_user_type, position, - *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetInt8(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_int8(cass_user_type, position, - *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetInt16(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_int16( - cass_user_type, position, *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetInt32(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_int32( - cass_user_type, position, *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetInt64(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_int64( - cass_user_type, position, *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetString(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_string( - cass_user_type, position, - boost::any_cast(udt_member)->c_str()); -} - -static CassError -CqlUdtSetBytes(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - CassBlob* blob_value = boost::any_cast(udt_member); - return cass_user_type_set_bytes(cass_user_type, position, - blob_value->data(), blob_value->size()); -} - -static CassError -CqlUdtSetUuid(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_uuid(cass_user_type, position, - *boost::any_cast(udt_member)); -} - -static CassError -CqlUdtSetUdt(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_user_type( - cass_user_type, position, - boost::any_cast(udt_member)->cass_user_type_); -} - -static CassError -CqlUdtSetCollection(const boost::any& udt_member, - const size_t& position, - CassUserType* cass_user_type) { - return cass_user_type_set_collection( - cass_user_type, position, boost::any_cast(udt_member)); -} -/// @} - -/// @name CqlCollectionAppend functions for binding data into Cassandra format -/// for insertion of a collection: -/// @{ -static CassError -CqlCollectionAppendNone(const boost::any& /* value */, - CassCollection* /* collection */) { - return CASS_OK; -} - -static CassError -CqlCollectionAppendBool(const boost::any& value, CassCollection* collection) { - return cass_collection_append_bool(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendInt8(const boost::any& value, CassCollection* collection) { - return cass_collection_append_int8(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendInt16(const boost::any& value, CassCollection* collection) { - return cass_collection_append_int16(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendInt32(const boost::any& value, CassCollection* collection) { - return cass_collection_append_int32(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendInt64(const boost::any& value, CassCollection* collection) { - return cass_collection_append_int64(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendString(const boost::any& value, CassCollection* collection) { - return cass_collection_append_string( - collection, boost::any_cast(value)->c_str()); -} - -static CassError -CqlCollectionAppendBytes(const boost::any& value, CassCollection* collection) { - CassBlob* blob_value = boost::any_cast(value); - return cass_collection_append_bytes(collection, blob_value->data(), - blob_value->size()); -} - -static CassError -CqlCollectionAppendUuid(const boost::any& value, CassCollection* collection) { - return cass_collection_append_uuid(collection, - *boost::any_cast(value)); -} - -static CassError -CqlCollectionAppendUdt(const boost::any& value, CassCollection* collection) { - Udt* udt = boost::any_cast(value); - size_t i = 0u; - for (boost::any& element : *udt) { - KEA_CASS_CHECK(CQL_FUNCTIONS[exchangeType(element)].cqlUdtSetFunction_( - element, i, udt->cass_user_type_)); - ++i; - } - return cass_collection_append_user_type(collection, udt->cass_user_type_); -} - -static CassError -CqlCollectionAppendCollection(const boost::any& value, - CassCollection* collection) { - return cass_collection_append_collection( - collection, boost::any_cast(value)); -} -// @} - /// @name CqlGet functions for retrieving data of the proper Cassandra format: /// @{ static CassError @@ -541,101 +242,30 @@ static CassError CqlGetUuid(const boost::any& data, const CassValue* value) { return cass_value_get_uuid(value, boost::any_cast(data)); } - -static CassError -CqlGetUdt(const boost::any& data, const CassValue* value) { - Udt* udt = boost::any_cast(data); - - CassIterator* fields = cass_iterator_fields_from_user_type(value); - if (!fields) { - isc_throw(DbOperationError, "CqlGetUdt(): column is not a UDT"); - } - Udt::const_iterator it = udt->begin(); - while (cass_iterator_next(fields)) { - const CassValue* field_value = - cass_iterator_get_user_type_field_value(fields); - if (cass_value_is_null(field_value)) { - isc_throw(DbOperationError, - "CqlGetUdt(): null value returned in UDT"); - } - const CassValueType& type = cass_value_type(field_value); - KEA_CASS_CHECK(CQL_FUNCTIONS[exchangeType(type)].cqlGetFunction_( - *it, field_value)); - ++it; - // If cqlGetFunction_() returns != CASS_OK, don't - // cass_iterator_free(items_iterator) because we're returning from this - // function and throwing from the callee. - } - cass_iterator_free(fields); - return CASS_OK; -} - -static CassError -CqlGetCollection(const boost::any& data, const CassValue* value) { - AnyCollection* collection = boost::any_cast(data); - if (!collection) { - isc_throw(DbOperationError, "CqlGetCollection(): column is not a collection"); - } - - BOOST_ASSERT(collection->size() == 1); - - /// @todo: Create a copy of the underlying object rather than referencing to - /// it. - boost::any underlying_object = *collection->begin(); - - collection->clear(); - - CassIterator* items = cass_iterator_from_collection(value); - if (!items) { - isc_throw(DbOperationError, - "CqlGetCollection(): column is not a collection"); - } - while (cass_iterator_next(items)) { - const CassValue* item_value = cass_iterator_get_value(items); - if (cass_value_is_null(item_value)) { - isc_throw(DbOperationError, - "CqlGetCollection(): null value returned in collection"); - } - const CassValueType& type = cass_value_type(item_value); - - collection->push_back(underlying_object); - KEA_CASS_CHECK(CQL_FUNCTIONS[exchangeType(type)].cqlGetFunction_( - *collection->rbegin(), item_value)); - // If cqlGetFunction_() returns != CASS_OK, don't call - // cass_iterator_free(items_iterator) because we're returning from this - // function and throwing from the callee. - } - cass_iterator_free(items); - return CASS_OK; -} /// @} /// @brief Functions used to interface with the Cassandra C++ driver CqlFunctionMap CQL_FUNCTIONS = // {{EXCHANGE_DATA_TYPE_NONE, - {CqlBindNone, CqlUdtSetNone, CqlCollectionAppendNone, CqlGetNone}}, + {CqlBindNone, CqlGetNone}}, {EXCHANGE_DATA_TYPE_BOOL, - {CqlBindBool, CqlUdtSetBool, CqlCollectionAppendBool, CqlGetBool}}, + {CqlBindBool, CqlGetBool}}, {EXCHANGE_DATA_TYPE_INT8, - {CqlBindInt8, CqlUdtSetInt8, CqlCollectionAppendInt8, CqlGetInt8}}, + {CqlBindInt8, CqlGetInt8}}, {EXCHANGE_DATA_TYPE_INT16, - {CqlBindInt16, CqlUdtSetInt16, CqlCollectionAppendInt16, CqlGetInt16}}, + {CqlBindInt16, CqlGetInt16}}, {EXCHANGE_DATA_TYPE_INT32, - {CqlBindInt32, CqlUdtSetInt32, CqlCollectionAppendInt32, CqlGetInt32}}, + {CqlBindInt32, CqlGetInt32}}, {EXCHANGE_DATA_TYPE_INT64, - {CqlBindInt64, CqlUdtSetInt64, CqlCollectionAppendInt64, CqlGetInt64}}, + {CqlBindInt64, CqlGetInt64}}, + {EXCHANGE_DATA_TYPE_TIMESTAMP, + {CqlBindInt64, CqlGetInt64}}, {EXCHANGE_DATA_TYPE_STRING, - {CqlBindString, CqlUdtSetString, CqlCollectionAppendString, - CqlGetString}}, + {CqlBindString, CqlGetString}}, {EXCHANGE_DATA_TYPE_BYTES, - {CqlBindBytes, CqlUdtSetBytes, CqlCollectionAppendBytes, CqlGetBytes}}, + {CqlBindBytes, CqlGetBytes}}, {EXCHANGE_DATA_TYPE_UUID, - {CqlBindUuid, CqlUdtSetUuid, CqlCollectionAppendUuid, CqlGetUuid}}, - {EXCHANGE_DATA_TYPE_UDT, - {CqlBindUdt, CqlUdtSetUdt, CqlCollectionAppendUdt, CqlGetUdt}}, - {EXCHANGE_DATA_TYPE_COLLECTION, - {CqlBindCollection, CqlUdtSetCollection, CqlCollectionAppendCollection, - CqlGetCollection}}}; + {CqlBindUuid, CqlGetUuid}}}; ExchangeDataType exchangeType(const boost::any& object) { @@ -656,24 +286,6 @@ exchangeType(const boost::any& object) { return exchange_type; } -ExchangeDataType -exchangeType(const CassValueType& type) { - CassTypeMap::const_iterator exchange_type_it = CASS_TYPE_MAP.find(type); - if (exchange_type_it == CASS_TYPE_MAP.end()) { - isc_throw(DbOperationError, - "exchangeType(): Cassandra value type " - << type << " does not map to any exchange type"); - } - const ExchangeDataType exchange_type = exchange_type_it->second; - if (exchange_type >= CQL_FUNCTIONS.size()) { - isc_throw(BadValue, - "exchangeType(): index " << exchange_type << " out of bounds " - << 0 << " - " - << CQL_FUNCTIONS.size() - 1); - } - return exchange_type; -} - void CqlCommon::bindData(const AnyArray& data, CassStatement* statement) { size_t i = 0u; @@ -812,7 +424,12 @@ CqlExchange::executeSelect(const CqlConnection& connection, const AnyArray& data } } - CqlCommon::bindData(local_data, statement); + try { + CqlCommon::bindData(local_data, statement); + } catch (const std::exception& ex) { + cass_statement_free(statement); + isc_throw(DbOperationError, ex.what()); + } // Everything's ready. Call the actual statement. future = cass_session_execute(connection.session_, statement); @@ -902,7 +519,12 @@ CqlExchange::executeMutation(const CqlConnection& connection, const AnyArray& da } } - CqlCommon::bindData(data, statement); + try { + CqlCommon::bindData(data, statement); + } catch (const std::exception& ex) { + cass_statement_free(statement); + isc_throw(DbOperationError, ex.what()); + } future = cass_session_execute(connection.session_, statement); if (!future) { diff --git a/src/lib/dhcpsrv/cql_exchange.h b/src/lib/dhcpsrv/cql_exchange.h index c99f762e41..5f17399ffa 100644 --- a/src/lib/dhcpsrv/cql_exchange.h +++ b/src/lib/dhcpsrv/cql_exchange.h @@ -56,38 +56,6 @@ class AnyArray : public std::vector { void remove(const size_t& index); }; -// @brief Representation of a Cassandra User Defined Type -class Udt : public AnyArray { -public: - /// @brief Parameterized constructor - Udt(const CqlConnection& connection, const std::string& name); - - /// @brief Destructor - ~Udt(); - - /// @brief Frees the underlying container. - void freeUserType(); - - /// @brief Creates the underlying container. - void newUserType(); - - /// @brief Connection to the Cassandra database - const CqlConnection& connection_; - - /// @brief Name of the UDT in the schema: CREATE TYPE ___ { ... } - const std::string name_; - - /// @brief Internal Cassandra driver object representing a Cassandra data - /// type - const CassDataType* cass_data_type_; - - /// @brief Internal Cassandra driver object representing a user defined type - CassUserType* cass_user_type_; -}; - -/// @brief Defines an array of arbitrary objects (used by Cassandra backend) -typedef AnyArray AnyCollection; - /// @brief Binds a C++ object to a Cassandra statement's parameter. Used in all /// statements. /// @param value the value to be set or retreived @@ -97,20 +65,6 @@ typedef CassError (*CqlBindFunction)(const boost::any& value, const size_t& index, CassStatement* statement); -/// @brief Sets a member in a UDT. Used in INSERT & UPDATE statements. -/// @param value the value to be set or retreived -/// @param index offset of the value being processed -/// @param cass_user_type pointer to the user type that uses this member -typedef CassError (*CqlUdtSetFunction)(const boost::any& value, - const size_t& index, - CassUserType* cass_user_type); - -/// @brief Sets an item in a collection. Used in INSERT & UPDATE statements. -/// @param value pointer to a value to be inserted or updated -/// @param collection pointer to collection to be inserted or updated -typedef CassError (*CqlCollectionAppendFunction)(const boost::any& value, - CassCollection* collection); - /// @brief Converts a single Cassandra column value to a C++ object. Used in /// SELECT statements. /// @@ -124,10 +78,6 @@ struct CqlFunction { /// @brief Binds a C++ object to a Cassandra statement's parameter. Used in /// all statements. CqlBindFunction cqlBindFunction_; - /// @brief Sets a member in a UDT. Used in INSERT & UPDATE statements. - CqlUdtSetFunction cqlUdtSetFunction_; - /// @brief Sets an item in a collection. Used in INSERT & UPDATE statements. - CqlCollectionAppendFunction cqlCollectionAppendFunction_; /// @brief Converts a single Cassandra column value to a C++ object. Used in /// SELECT statements. CqlGetFunction cqlGetFunction_; @@ -314,10 +264,6 @@ class CqlCommon { ExchangeDataType exchangeType(const boost::any& object); -/// @brief Determine exchange type based on CassValueType. -ExchangeDataType -exchangeType(const CassValueType& type); - } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/cql_host_data_source.cc b/src/lib/dhcpsrv/cql_host_data_source.cc index ae8787359c..0198b169f7 100644 --- a/src/lib/dhcpsrv/cql_host_data_source.cc +++ b/src/lib/dhcpsrv/cql_host_data_source.cc @@ -1163,12 +1163,12 @@ CqlHostExchange::retrieve() { asiolink::IOAddress ipv4_reservation = asiolink::IOAddress(static_cast(host_ipv4_address_)); - Host* host = new Host(host_identifier.data(), host_identifier.size(), + HostPtr host(new Host(host_identifier.data(), host_identifier.size(), host_identifier_type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation, hostname_, host_ipv4_client_classes_, host_ipv6_client_classes_, static_cast(host_ipv4_next_server_), - host_ipv4_server_hostname_, host_ipv4_boot_file_name_); + host_ipv4_server_hostname_, host_ipv4_boot_file_name_)); // Set the user context if there is one. if (!user_context_.empty()) { @@ -1926,7 +1926,7 @@ CqlHostDataSourceImpl::getHostCollection(StatementTag statement_tag, // Form HostPtr objects. HostCollection host_collection; for (boost::any& host : collection) { - host_collection.push_back(HostPtr(boost::any_cast(host))); + host_collection.push_back(boost::any_cast(host)); } // Merge the denormalized table entries that belong to the same host diff --git a/src/lib/dhcpsrv/cql_lease_mgr.cc b/src/lib/dhcpsrv/cql_lease_mgr.cc index a225585ab2..6bf18242be 100644 --- a/src/lib/dhcpsrv/cql_lease_mgr.cc +++ b/src/lib/dhcpsrv/cql_lease_mgr.cc @@ -224,8 +224,8 @@ class CqlLease4Exchange : public CqlLeaseExchange { private: // Pointer to lease object Lease4Ptr lease_; - // IPv4 address - cass_int32_t address_; + // IPv4 address plus port + cass_int64_t address_; // Client identification CassBlob client_id_; }; // CqlLease4Exchange @@ -376,10 +376,10 @@ CqlLease4Exchange::createBindForInsert(const Lease4Ptr &lease, AnyArray &data) { // structure. try { - // address: int + // address: bigint // The address in the Lease structure is an IOAddress object. // Convert this to an integer for storage. - address_ = static_cast(lease->addr_.toUint32()); + address_ = static_cast(lease_->addr_.addressPlusPortToUint64()); // hwaddr: blob if (lease_->hwaddr_ && lease_->hwaddr_->hwaddr_.size() > 0) { @@ -412,8 +412,7 @@ CqlLease4Exchange::createBindForInsert(const Lease4Ptr &lease, AnyArray &data) { // For convenience for external tools, this is converted to lease // expiry time (expire). The relationship is given by: // expire = cltt_ + valid_lft_ - CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, - expire_); + CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, expire_); // subnet_id: int subnet_id_ = static_cast(lease_->subnet_id_); @@ -470,10 +469,10 @@ CqlLease4Exchange::createBindForUpdate(const Lease4Ptr &lease, AnyArray &data, // structure. try { - // address: int + // address: bigint // The address in the Lease structure is an IOAddress object. // Convert this to an integer for storage. - address_ = static_cast(lease->addr_.toUint32()); + address_ = static_cast(lease_->addr_.addressPlusPortToUint64()); // hwaddr: blob if (lease_->hwaddr_ && lease_->hwaddr_->hwaddr_.size() > 0) { @@ -506,8 +505,7 @@ CqlLease4Exchange::createBindForUpdate(const Lease4Ptr &lease, AnyArray &data, // For convenience for external tools, this is converted to lease // expiry time (expire). The relationship is given by: // expire = cltt_ + valid_lft_ - CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, - expire_); + CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, expire_); // subnet_id: int subnet_id_ = static_cast(lease_->subnet_id_); @@ -559,8 +557,8 @@ CqlLease4Exchange::createBindForDelete(const IOAddress &address, AnyArray &data, // structure. try { - // address: int - address_ = static_cast(address.toUint32()); + // address: bigint + address_ = static_cast(address.addressPlusPortToUint64()); // Start with a fresh array. data.clear(); @@ -643,7 +641,7 @@ CqlLease4Exchange::retrieve() { HWAddrPtr hwaddr(new HWAddr(hwaddr_, HTYPE_ETHER)); - uint32_t addr4 = static_cast(address_); + uint64_t addr4 = static_cast(address_); Lease4Ptr result(new Lease4(addr4, hwaddr, client_id_.data(), client_id_.size(), valid_lifetime_, 0, 0, @@ -1131,8 +1129,7 @@ CqlLease6Exchange::createBindForUpdate(const Lease6Ptr &lease, AnyArray &data, // For convenience for external tools, this is converted to lease // expiry time (expire). The relationship is given by: // expire = cltt_ + valid_lft_ - CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, - expire_); + CqlExchange::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_, expire_); // subnet_id: int subnet_id_ = static_cast(lease_->subnet_id_); @@ -1459,38 +1456,38 @@ CqlLease6Exchange::getExpiredLeases(const size_t &max_leases, /// This class provides the functionality such as results storage and row /// fetching common to fulfilling the statistical lease data query. /// -class CqlLeaseStatsQuery : public LeaseStatsQuery { +class CqlLeaseStatsQuery : public LeaseStatsQuery, public CqlExchange { public: /// @brief Constructor to query for all subnets' stats /// /// The query created will return statistics for all subnets /// - /// @param conn An open connection to the database housing the lease data + /// @param connection An open connection to the database housing the lease data /// @param statement The lease data SQL prepared statement tag to execute /// @param fetch_type Indicates whether or not lease_type should be /// fetched from the result set (should be true for v6) - CqlLeaseStatsQuery(CqlConnection& conn, StatementTag& statement, - const bool fetch_type) - : conn_(conn), statement_(statement), fetch_type_(fetch_type), + CqlLeaseStatsQuery(CqlConnection& connection, StatementTag& statement, + const bool fetch_type) + : connection_(connection), statement_(statement), fetch_type_(fetch_type), cummulative_rows_(), next_row_(cummulative_rows_.begin()), - subnet_id_(0), lease_type_(0), lease_state_(0) { + subnet_id_(0), lease_type_(0), state_(0) { } /// @brief Constructor to query for a single subnet's stats /// /// The query created will return statistics for a single subnet /// - /// @param conn An open connection to the database housing the lease data + /// @param connection An open connection to the database housing the lease data /// @param statement The lease data SQL prepared statement tag to execute /// @param fetch_type Indicates whether or not lease_type should be /// fetched from the result set (should be true for v6) /// @param subnet_id id of the subnet for which stats are desired - CqlLeaseStatsQuery(CqlConnection& conn, StatementTag& statement, - const bool fetch_type, const SubnetID& subnet_id) - : LeaseStatsQuery(subnet_id), conn_(conn), statement_(statement), + CqlLeaseStatsQuery(CqlConnection& connection, StatementTag& statement, + const bool fetch_type, const SubnetID& subnet_id) + : LeaseStatsQuery(subnet_id), connection_(connection), statement_(statement), fetch_type_(fetch_type), cummulative_rows_(), next_row_(cummulative_rows_.begin()), - subnet_id_(0), lease_type_(0), lease_state_(0) { + subnet_id_(0), lease_type_(0), state_(0) { } /// @brief Constructor to query for the stats for a range of subnets @@ -1498,19 +1495,19 @@ class CqlLeaseStatsQuery : public LeaseStatsQuery { /// The query created will return statistics for the inclusive range of /// subnets described by first and last sunbet IDs. /// - /// @param conn An open connection to the database housing the lease data + /// @param connection An open connection to the database housing the lease data /// @param statement The lease data SQL prepared statement tag to execute /// @param fetch_type Indicates whether or not lease_type should be /// fetched from the result set (should be true for v6) /// @param first_subnet_id first subnet in the range of subnets /// @param last_subnet_id last subnet in the range of subnets - CqlLeaseStatsQuery(CqlConnection& conn, StatementTag& statement, - const bool fetch_type, const SubnetID& first_subnet_id, - const SubnetID& last_subnet_id) - : LeaseStatsQuery(first_subnet_id, last_subnet_id), conn_(conn), + CqlLeaseStatsQuery(CqlConnection& connection, StatementTag& statement, + const bool fetch_type, const SubnetID& first_subnet_id, + const SubnetID& last_subnet_id) + : LeaseStatsQuery(first_subnet_id, last_subnet_id), connection_(connection), statement_(statement), fetch_type_(fetch_type), cummulative_rows_(), next_row_(cummulative_rows_.begin()), - subnet_id_(0), lease_type_(0), lease_state_(0) { + subnet_id_(0), lease_type_(0), state_(0) { } /// @brief Destructor @@ -1524,35 +1521,6 @@ class CqlLeaseStatsQuery : public LeaseStatsQuery { /// first row of the aggregate results. void start(); - /// @brief Executes protocol specific lease query SELECT statement - /// - /// Currently we do not have a good way for Cassandra to roll up the - /// lease counts per subnet, type, and state as we do the other back - /// ends. This method executes the select statement which returns - /// a result set containing a row of data for every lease: - /// -v4 - subnet-id, lease-state - /// -v6 - subnet-id, lease-type, lease-state - /// - /// It then iterates over this result set, aggregating the data into a - /// a map of LeaseStatRows. - /// - /// If we didn't have to roll up the raw lease data first, we could - /// have derived this class from CqlExchange and used it's executeSelect - /// (from which this method borrows heavily). However, that would mean - /// copying all the raw lease data into a collection returned by - /// executeSelect and then aggregating that into cummulative rows. - /// The way we are now we go turn the raw lease data directly into the - /// cummulative row map. - /// - /// @param connection connection used to communicate with the Cassandra - /// database - /// @param data array of bound objects used to filter the results - /// @param statement_tag prepared statement being executed - /// - /// @throw DbOperationError - void executeSelect(const CqlConnection& connection, const AnyArray& data, - StatementTag statement_tag); - /// @brief Fetches the next row in the result set /// /// Once the internal result set has been populated by invoking the @@ -1576,6 +1544,15 @@ class CqlLeaseStatsQuery : public LeaseStatsQuery { virtual void createBindForSelect(AnyArray& data, StatementTag statement_tag = NULL); + /// @brief Copy received data into the derived class' object. + /// + /// Copies information about the entity to be retrieved into a holistic + /// object. Called in @ref executeSelect(). Not implemented for base class + /// CqlExchange. To be implemented in derived classes. + /// + /// @return a pointer to the object retrieved. + virtual boost::any retrieve(); + /// @brief Statement tags definitions /// @{ // Return lease4 lease statistics for all subnets @@ -1584,7 +1561,6 @@ class CqlLeaseStatsQuery : public LeaseStatsQuery { static constexpr StatementTag SUBNET_LEASE4_STATS = "SUBNET_LEASE4_STATS"; /// Return lease4 lease statistics for a range of subnets static constexpr StatementTag SUBNET_RANGE_LEASE4_STATS = "SUBNET_RANGE_LEASE4_STATS"; - // Return lease6 lease statistics for all subnets static constexpr StatementTag ALL_LEASE6_STATS = "ALL_LEASE6_STATS"; /// Return lease6 lease statistics for a single subnet @@ -1597,28 +1573,29 @@ class CqlLeaseStatsQuery : public LeaseStatsQuery { static StatementMap tagged_statements_; private: - /// @brief Database connection to use to execute the query - CqlConnection& conn_; + /// @brief Database connection + const CqlConnection &connection_; /// @brief The query's prepared statement tag StatementTag statement_; - /// @brief Indicates if query supplies lease type + /// @brief fetch from the result set? (should be true for v6) bool fetch_type_; - /// @brief map containing the aggregated lease counts std::map cummulative_rows_; /// @brief cursor pointing to the next row to read in aggregate map std::map::iterator next_row_; - /// @brief bind variable for retrieving subnet-id from a result set row - int subnet_id_; - /// @brief bind variable for retrieving lease-type from a result set row - int lease_type_; - /// @brief bind variable for retrieving lease-state from a result set row - int lease_state_; + /// @brief Subnet identifier + cass_int32_t subnet_id_; + + /// @brief Lease type (NA, TA or PD) + cass_int32_t lease_type_; + + /// @brief Lease state + cass_int32_t state_; }; constexpr StatementTag CqlLeaseStatsQuery::ALL_LEASE4_STATS; @@ -1682,12 +1659,10 @@ StatementMap CqlLeaseStatsQuery::tagged_statements_{ "WHERE subnet_id >= ? and subnet_id <= ? " "ALLOW FILTERING " }}, - }; void CqlLeaseStatsQuery::start() { - // Set up where clause parameters as needed AnyArray data; cass_int32_t first_subnet_id_data; @@ -1701,11 +1676,24 @@ CqlLeaseStatsQuery::start() { data.add(&last_subnet_id_data); } } - - // This gets a collection of "raw" data for all leases that match - // the subnet selection criteria (all, range, or single subnets) - // then rolls them up into cummulative_rows_ - executeSelect(conn_, data, statement_); + AnyArray collection = executeSelect(connection_, data, statement_); + + // Form LeaseStatsRowPtr objects. + LeaseStatsCollection stats_collection; + for (boost::any& stats : collection) { + LeaseStatsRowPtr data(boost::any_cast(stats)); + if (data->lease_state_ != Lease::STATE_DEFAULT && + data->lease_state_ != Lease::STATE_DECLINED) { + continue; + } + stats_collection.push_back(data); + auto cum_row = cummulative_rows_.find(*data); + if (cum_row != cummulative_rows_.end()) { + cummulative_rows_[*data] = cum_row->second + 1; + } else { + cummulative_rows_.insert(std::make_pair(*data, 1)); + } + } // Set our row iterator to the beginning next_row_ = cummulative_rows_.begin(); @@ -1732,123 +1720,29 @@ CqlLeaseStatsQuery::getNextRow(LeaseStatsRow& row) { } void -CqlLeaseStatsQuery::createBindForSelect(AnyArray& data, StatementTag) { +CqlLeaseStatsQuery::createBindForSelect(AnyArray& data, StatementTag /* statement_tag */) { + + // Start with a fresh array. data.clear(); + + // subnet_id: int data.add(&subnet_id_); + + // lease_type: int if (fetch_type_) { data.add(&lease_type_); - } - - data.add(&lease_state_); -} - -void -CqlLeaseStatsQuery::executeSelect(const CqlConnection& connection, const AnyArray& data, - StatementTag statement_tag) { - CassError rc; - CassStatement* statement = NULL; - CassFuture* future = NULL; - AnyArray local_data = data; - - // Find the query statement first. - StatementMap::const_iterator it = connection.statements_.find(statement_tag); - if (it == connection.statements_.end()) { - isc_throw(DbOperationError, - "CqlLeastStatsQuery::executeSelect(): Statement " - << statement_tag << "has not been prepared."); - } - - // Bind the data before the query is executed. - CqlTaggedStatement tagged_statement = it->second; - if (tagged_statement.is_raw_) { - // The entire query is the first element in data. - std::string* query = boost::any_cast(local_data.back()); - local_data.pop_back(); - statement = cass_statement_new(query->c_str(), local_data.size()); } else { - statement = cass_prepared_bind(tagged_statement.prepared_statement_); - if (!statement) { - isc_throw(DbOperationError, - "CqlLeaseStatsQuery::executeSelect(): unable to bind statement " - << tagged_statement.name_); - } - } - - // Set specific level of consistency if we're told to do so. - if (connection.force_consistency_) { - rc = cass_statement_set_consistency(statement, connection.consistency_); - if (rc != CASS_OK) { - cass_statement_free(statement); - isc_throw(DbOperationError, - "CqlLeaseStatsQuery::executeSelect(): unable to set statement " - "consistency for statement " - << tagged_statement.name_ - << ", Cassandra error code: " << cass_error_desc(rc)); - } - } - - CqlCommon::bindData(local_data, statement); - - // Everything's ready. Call the actual statement. - future = cass_session_execute(connection.session_, statement); - if (!future) { - cass_statement_free(statement); - isc_throw(DbOperationError, - "CqlLeaseStatsQuery::executeSelect(): no CassFuture for statement " - << tagged_statement.name_); + lease_type_ = Lease::TYPE_NA; // lease type is always NA for v4 } - // Wait for the statement execution to complete. - cass_future_wait(future); - const std::string error = connection.checkFutureError( - "CqlLeaseStatsQuery::executeSelect(): cass_session_execute() != CASS_OK", - future, statement_tag); - rc = cass_future_error_code(future); - if (rc != CASS_OK) { - cass_future_free(future); - cass_statement_free(statement); - isc_throw(DbOperationError, error); - } - - // Get column values. - const CassResult* result_collection = cass_future_get_result(future); - - // lease type is always NA for v4 - if (!fetch_type_) { - lease_type_ = Lease::TYPE_NA; - } - - // Since we're currently forced to pull data for all leases, we - // iterate over them, aggregating them into cummulative LeaseStatsRows - AnyArray return_values; - CassIterator* rows = cass_iterator_from_result(result_collection); - while (cass_iterator_next(rows)) { - const CassRow* row = cass_iterator_get_row(rows); - createBindForSelect(return_values, statement_tag); - CqlCommon::getData(row, return_values); - - if (lease_state_ != Lease::STATE_DEFAULT && - lease_state_ != Lease::STATE_DECLINED) { - continue; - } - - LeaseStatsRow raw_row(subnet_id_, static_cast(lease_type_), - lease_state_, 1); - - auto cum_row = cummulative_rows_.find(raw_row); - if (cum_row != cummulative_rows_.end()) { - cummulative_rows_[raw_row] = cum_row->second + 1; - } else { - cummulative_rows_.insert(std::make_pair(raw_row, 1)); - } - } + // state: int + data.add(&state_); +} - // Free resources. - cass_iterator_free(rows); - cass_result_free(result_collection); - cass_future_free(future); - cass_statement_free(statement); - return; +boost::any +CqlLeaseStatsQuery::retrieve() { + return (LeaseStatsRowPtr(new LeaseStatsRow(subnet_id_, + static_cast(lease_type_), state_, 1))); } CqlLeaseMgr::CqlLeaseMgr(const DatabaseConnection::ParameterMap ¶meters) @@ -1935,8 +1829,8 @@ CqlLeaseMgr::getLease4(const IOAddress &addr) const { // Set up the WHERE clause value AnyArray data; - cass_int32_t address = static_cast(addr.toUint32()); - data.add(&address); + cass_int64_t addr4 = static_cast(addr.addressPlusPortToUint64()); + data.add(&addr4); // Get the data. Lease4Ptr result; @@ -2299,7 +2193,7 @@ CqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) { DHCPSRV_CQL_DELETE_EXPIRED_RECLAIMED6) .arg(secs); AnyArray data; - uint64_t n_of_deleted_leases = 0u; + uint64_t deleted = 0u; cass_int32_t limit = 1024; // State is reclaimed. @@ -2318,10 +2212,10 @@ CqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) { exchange6->getLeaseCollection(CqlLease6Exchange::GET_LEASE6_EXPIRE, data, leases); for (Lease6Ptr &lease : leases) { if (deleteLease(lease->addr_)) { - ++n_of_deleted_leases; + ++deleted; } } - return n_of_deleted_leases; + return (deleted); } LeaseStatsQueryPtr @@ -2344,7 +2238,7 @@ CqlLeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) { LeaseStatsQueryPtr CqlLeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id, - const SubnetID& last_subnet_id) { + const SubnetID& last_subnet_id) { LeaseStatsQueryPtr query( new CqlLeaseStatsQuery(dbconn_, CqlLeaseStatsQuery::SUBNET_RANGE_LEASE4_STATS, false, first_subnet_id, last_subnet_id)); @@ -2372,7 +2266,7 @@ CqlLeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) { LeaseStatsQueryPtr CqlLeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id, - const SubnetID& last_subnet_id) { + const SubnetID& last_subnet_id) { LeaseStatsQueryPtr query( new CqlLeaseStatsQuery(dbconn_, CqlLeaseStatsQuery::SUBNET_RANGE_LEASE6_STATS, true, first_subnet_id, last_subnet_id)); diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc index 9f52c6603d..ece4f3c061 100644 --- a/src/lib/dhcpsrv/lease.cc +++ b/src/lib/dhcpsrv/lease.cc @@ -28,9 +28,9 @@ Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2, uint32_t valid_lft, SubnetID subnet_id, time_t cltt, const bool fqdn_fwd, const bool fqdn_rev, const std::string& hostname, const HWAddrPtr& hwaddr) - :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt), - subnet_id_(subnet_id), hostname_(hostname), fqdn_fwd_(fqdn_fwd), - fqdn_rev_(fqdn_rev), hwaddr_(hwaddr), state_(STATE_DEFAULT) { + : addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt), + subnet_id_(subnet_id), hostname_(hostname), fqdn_fwd_(fqdn_fwd), + fqdn_rev_(fqdn_rev), hwaddr_(hwaddr), state_(STATE_DEFAULT) { } diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h index d8869b5af8..eeeb993283 100644 --- a/src/lib/dhcpsrv/lease.h +++ b/src/lib/dhcpsrv/lease.h @@ -310,8 +310,7 @@ struct Lease4 : public Lease { /// @brief Default constructor /// /// Initialize fields that don't have a default constructor. - Lease4() : Lease(0, 0, 0, 0, 0, 0, false, false, "", HWAddrPtr()) - { + Lease4() : Lease(0U, 0, 0, 0, 0, 0, false, false, "", HWAddrPtr()) { } /// @brief Copy constructor @@ -463,7 +462,6 @@ typedef boost::shared_ptr Lease6Ptr; /// would be required. As this is a critical part of the code that will be used /// extensively, direct access is warranted. struct Lease6 : public Lease { - /// @brief Lease type /// /// One of normal address, temporary address, or prefix. diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index a820304ae2..7f60fffb7d 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -220,6 +220,9 @@ typedef boost::shared_ptr LeaseStatsQueryPtr; /// @brief Defines a pointer to a LeaseStatsRow. typedef boost::shared_ptr LeaseStatsRowPtr; +/// @brief Collection of the @c LeaseStatsRow objects. +typedef std::vector LeaseStatsCollection; + /// @brief Abstract Lease Manager /// /// This is an abstract API for lease database backends. It provides unified diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc index a4de082a5f..e99d5aa15f 100644 --- a/src/lib/dhcpsrv/mysql_connection.cc +++ b/src/lib/dhcpsrv/mysql_connection.cc @@ -42,7 +42,10 @@ MySqlTransaction::~MySqlTransaction() { // Rollback if the MySqlTransaction::commit wasn't explicitly // called. if (!committed_) { - conn_.rollback(); + try { + conn_.rollback(); + } catch (...) { + } } } @@ -131,10 +134,8 @@ MySqlConnection::openDatabase() { // No timeout parameter, we are going to use the default timeout. stimeout = ""; } - if (stimeout.size() > 0) { // Timeout was given, so try to convert it to an integer. - try { connect_timeout = boost::lexical_cast(stimeout); } catch (...) { diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h index b1d22d090b..88716d26cd 100644 --- a/src/lib/dhcpsrv/mysql_connection.h +++ b/src/lib/dhcpsrv/mysql_connection.h @@ -40,7 +40,7 @@ extern const int MLM_MYSQL_FETCH_FAILURE; /// @name Current database schema version values. //@{ -const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 6; +const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 7; const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0; //@} diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 37eb903b5d..ef907d907e 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -2232,7 +2232,6 @@ TaggedStatementArray tagged_statements = { { "h.dhcp_identifier_type, h.dhcp4_subnet_id, " "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, " "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, " - "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, " "o.option_id, o.code, o.value, o.formatted_value, o.space, " "o.persistent, o.user_context, " @@ -2279,11 +2278,11 @@ TaggedStatementArray tagged_statements = { { "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"}, {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, - "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? " + "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type = ? " "AND dhcp_identifier = ?"}, {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, - "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? " + "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type = ? " "AND dhcp_identifier = ?"} } diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index 93593e9054..658a791b8f 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -411,11 +411,11 @@ class MySqlLease4Exchange : public MySqlLeaseExchange { // structure. try { - // address: uint32_t + // address: uint64_t // The address in the Lease structure is an IOAddress object. Convert // this to an integer for storage. - addr4_ = lease_->addr_.toUint32(); - bind_[0].buffer_type = MYSQL_TYPE_LONG; + addr4_ = lease_->addr_.addressPlusPortToUint64(); + bind_[0].buffer_type = MYSQL_TYPE_LONGLONG; bind_[0].buffer = reinterpret_cast(&addr4_); bind_[0].is_unsigned = MLM_TRUE; // bind_[0].is_null = &MLM_FALSE; // commented out for performance @@ -553,8 +553,8 @@ class MySqlLease4Exchange : public MySqlLeaseExchange { // code that explicitly sets is_null is there, but is commented out. memset(bind_, 0, sizeof(bind_)); - // address: uint32_t - bind_[0].buffer_type = MYSQL_TYPE_LONG; + // address: uint64_t + bind_[0].buffer_type = MYSQL_TYPE_LONGLONG; bind_[0].buffer = reinterpret_cast(&addr4_); bind_[0].is_unsigned = MLM_TRUE; // bind_[0].is_null = &MLM_FALSE; // commented out for performance @@ -702,7 +702,7 @@ class MySqlLease4Exchange : public MySqlLeaseExchange { // Note: All array lengths are equal to the corresponding variable in the // schema. // Note: Arrays are declared fixed length for speed of creation - uint32_t addr4_; ///< IPv4 address + uint64_t addr4_; ///< IPv4 address plus port MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array std::string columns_[LEASE_COLUMNS]; ///< Column names my_bool error_[LEASE_COLUMNS]; ///< Error array @@ -1735,8 +1735,8 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const { MYSQL_BIND inbind[1]; memset(inbind, 0, sizeof(inbind)); - uint32_t addr4 = addr.toUint32(); - inbind[0].buffer_type = MYSQL_TYPE_LONG; + uint64_t addr4 = addr.addressPlusPortToUint64(); + inbind[0].buffer_type = MYSQL_TYPE_LONGLONG; inbind[0].buffer = reinterpret_cast(&addr4); inbind[0].is_unsigned = MLM_TRUE; @@ -2153,8 +2153,8 @@ MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) { MYSQL_BIND where; memset(&where, 0, sizeof(where)); - uint32_t addr4 = lease->addr_.toUint32(); - where.buffer_type = MYSQL_TYPE_LONG; + uint64_t addr4 = lease->addr_.addressPlusPortToUint64(); + where.buffer_type = MYSQL_TYPE_LONGLONG; where.buffer = reinterpret_cast(&addr4); where.is_unsigned = MLM_TRUE; bind.push_back(where); @@ -2224,9 +2224,9 @@ MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) { memset(inbind, 0, sizeof(inbind)); if (addr.isV4()) { - uint32_t addr4 = addr.toUint32(); + uint64_t addr4 = addr.addressPlusPortToUint64(); - inbind[0].buffer_type = MYSQL_TYPE_LONG; + inbind[0].buffer_type = MYSQL_TYPE_LONGLONG; inbind[0].buffer = reinterpret_cast(&addr4); inbind[0].is_unsigned = MLM_TRUE; diff --git a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc index 1665b74f7c..aaba0a1f9b 100644 --- a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc +++ b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc @@ -65,8 +65,9 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db, // 2. Update the copy with the passed keywords. BOOST_FOREACH(ConfigPair param, database_config->mapValue()) { try { - if ((param.first == "persist") || (param.first == "readonly") || - (param.first == "tcp-nodelay")) { + if ((param.first == "persist") || + (param.first == "tcp-nodelay") || + (param.first == "readonly")) { values_copy[param.first] = (param.second->boolValue() ? "true" : "false"); @@ -106,6 +107,14 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db, boost::lexical_cast(port); } else { + // all remaining string parameters + // type + // user + // password + // host + // name + // contact-points + // keyspace values_copy[param.first] = param.second->stringValue(); } } catch (const isc::data::TypeError& ex) { @@ -189,17 +198,17 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db, } // Check that request_timeout value makes sense. - if ((reconnect_wait_time < 0) || - (reconnect_wait_time > std::numeric_limits::max())) { - ConstElementPtr value = database_config->get("reconnect-wait-time"); - isc_throw(DhcpConfigError, "reconnect-wait-time " << reconnect_wait_time + if ((request-timeout < 0) || + (request-timeout > std::numeric_limits::max())) { + ConstElementPtr value = database_config->get("request-timeout"); + isc_throw(DhcpConfigError, "request-timeout " << request-timeout << " must be in range 0...MAX_UINT32 (4294967295) " << " (" << value->getPosition() << ")"); } // Check that tcp_keepalive value makes sense. if ((tcp_keepalive < 0) || (tcp_keepalive > std::numeric_limits::max())) { - ConstElementPtr value = database_config->get("reconnect-wait-time"); + ConstElementPtr value = database_config->get("tcp_keepalive"); isc_throw(DhcpConfigError, "tcp-keepalive " << tcp_keepalive << " must be in range 0...MAX_UINT32 (4294967295) " << " (" << value->getPosition() << ")"); diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 6f6209620c..df7df844a8 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -851,6 +851,32 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, /// client-class processing is now generic and handled in the common /// code (see isc::data::SubnetConfigParser::createSubnet) + // address plus port specific parameter: v4-psid-offset. If not explicitly specified, + // it will have the default value of "0". + uint8_t psid_offset = getInteger(params, "v4-psid-offset"); + if (psid_offset) { + subnet4->get4o6().setPsidOffset(psid_offset); + subnet4->get4o6().enabled(true); + } + + // address plus port specific parameter: v4-psid-len. If not explicitly + // specified, + // it will have the default value of "0". + uint8_t psid_len = getInteger(params, "v4-psid-len"); + if (psid_len) { + subnet4->get4o6().setPsidLen(psid_len); + subnet4->get4o6().enabled(true); + } + + ConstElementPtr excluded_psids = params->get("v4-excluded-psids"); + if (excluded_psids) { + BOOST_FOREACH (ConstElementPtr psid, excluded_psids->listValue()) { + int64_t value; + psid->getValue(value); + subnet4->getExcludedPSIDs().insert(static_cast(value)); + } + } + // Here globally defined options were merged to the subnet specific // options but this is no longer the case (they have a different // and not consecutive priority). @@ -1329,7 +1355,7 @@ D2ClientConfigParser::parse(isc::data::ConstElementPtr client_config) { found_qualifying_suffix = true; } - IOAddress sender_ip(0); + IOAddress sender_ip(0U); if (sender_ip_str.empty()) { // The default sender IP depends on the server IP family sender_ip = (server_ip.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc index 4fe78bd190..90abff5c77 100644 --- a/src/lib/dhcpsrv/parsers/simple_parser4.cc +++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc @@ -83,6 +83,8 @@ const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = { { "4o6-interface", Element::string, "" }, { "4o6-interface-id", Element::string, "" }, { "4o6-subnet", Element::string, "" }, + { "v4-psid-offset", Element::integer, "0" }, + { "v4-psid-len", Element::integer, "0" }, }; /// @brief This table defines default values for each IPv4 subnet that is @@ -96,6 +98,8 @@ const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = { { "4o6-interface", Element::string, "" }, { "4o6-interface-id", Element::string, "" }, { "4o6-subnet", Element::string, "" }, + { "v4-psid-offset", Element::integer, "0" }, + { "v4-psid-len", Element::integer, "0" }, }; /// @brief This table defines default values for each IPv4 shared network. diff --git a/src/lib/dhcpsrv/pgsql_connection.cc b/src/lib/dhcpsrv/pgsql_connection.cc index a64ce7f779..ff6b83db84 100644 --- a/src/lib/dhcpsrv/pgsql_connection.cc +++ b/src/lib/dhcpsrv/pgsql_connection.cc @@ -103,7 +103,10 @@ PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn) PgSqlTransaction::~PgSqlTransaction() { // If commit() wasn't explicitly called, rollback. if (!committed_) { - conn_.rollback(); + try { + conn_.rollback(); + } catch (...) { + } } } diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index f6e2a18434..e9ac66b056 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -400,7 +400,7 @@ class PgSqlLease4Exchange : public PgSqlLeaseExchange { try { addr_str_ = boost::lexical_cast - (lease->addr_.toUint32()); + (lease->addr_.addressPlusPortToUint64()); bind_array.add(addr_str_); if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) { @@ -510,7 +510,7 @@ class PgSqlLease4Exchange : public PgSqlLeaseExchange { Lease4Ptr lease_; /// @brief Lease4 specific members for binding and conversion. - uint32_t addr4_; + uint64_t addr4_; size_t hwaddr_length_; std::vector hwaddr_; uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN]; @@ -1152,7 +1152,7 @@ PgSqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const { // LEASE ADDRESS std::string addr_str = boost::lexical_cast - (addr.toUint32()); + (addr.addressPlusPortToUint64()); bind_array.add(addr_str); // Get the data @@ -1499,9 +1499,9 @@ PgSqlLeaseMgr::updateLease4(const Lease4Ptr& lease) { exchange4_->createBindForSend(lease, bind_array); // Set up the WHERE clause and append it to the SQL_BIND array - std::string addr4_ = boost::lexical_cast - (lease->addr_.toUint32()); - bind_array.add(addr4_); + std::string addr4_str = boost::lexical_cast + (lease->addr_.addressPlusPortToUint64()); + bind_array.add(addr4_str); // Drop to common update code updateLeaseCommon(stindex, bind_array, lease); @@ -1551,7 +1551,7 @@ PgSqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) { if (addr.isV4()) { std::string addr4_str = boost::lexical_cast - (addr.toUint32()); + (addr.addressPlusPortToUint64()); bind_array.add(addr4_str); return (deleteLeaseCommon(DELETE_LEASE4, bind_array) > 0); } diff --git a/src/lib/dhcpsrv/sql_common.h b/src/lib/dhcpsrv/sql_common.h index 610abd62e1..a8795e91df 100644 --- a/src/lib/dhcpsrv/sql_common.h +++ b/src/lib/dhcpsrv/sql_common.h @@ -32,9 +32,7 @@ enum ExchangeDataType { EXCHANGE_DATA_TYPE_TIMESTAMP, EXCHANGE_DATA_TYPE_STRING, EXCHANGE_DATA_TYPE_BYTES, - EXCHANGE_DATA_TYPE_UUID, - EXCHANGE_DATA_TYPE_UDT, ///< User-Defined Type (used in Cassandra) - EXCHANGE_DATA_TYPE_COLLECTION ///< Collection (used in Cassandra) + EXCHANGE_DATA_TYPE_UUID }; /// @brief Base class for backend exchanges. diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index ae4aefe306..8fd9b36cb0 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -300,6 +300,13 @@ isc::asiolink::IOAddress Subnet4::getSiaddr() const { return (siaddr_); } +bool Subnet4::isExcludedAddress(const asiolink::IOAddress& address) { + if (address.getPsidLen()) { + return excludedPSIDs.find(address.getPsid()) != excludedPSIDs.end(); + } + return false; +} + void Subnet4::setSname(const std::string& sname) { sname_ = sname; } diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 176e3dbb43..211be63f45 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -30,6 +30,8 @@ namespace isc { namespace dhcp { +typedef std::set PSIDContainer; + class Subnet : public virtual UserContext, public data::CfgToElement { // Assignable network is our friend to allow it to call @@ -536,6 +538,18 @@ class Subnet4 : public Subnet, public Network4 { return (dhcp4o6_); } + /// @brief Returns container with excluded PSIDs. + /// + /// @return container with excluded PSIDs + PSIDContainer& getExcludedPSIDs() { + return (excludedPSIDs); + } + + /// @brief Returns if address contains excluded PSID. + /// + /// @return true if address contains excluded PSID, false otherwise + bool isExcludedAddress(const asiolink::IOAddress& address); + /// @brief Unparse a subnet object. /// /// @return A pointer to unparsed subnet configuration. @@ -568,6 +582,9 @@ class Subnet4 : public Subnet, public Network4 { /// @brief All the information related to DHCP4o6 Cfg4o6 dhcp4o6_; + + /// @brief container with excluded PSIDs + PSIDContainer excludedPSIDs; }; class Subnet6; diff --git a/src/lib/dhcpsrv/subnet_selector.h b/src/lib/dhcpsrv/subnet_selector.h index 559fac5633..e9561c6385 100644 --- a/src/lib/dhcpsrv/subnet_selector.h +++ b/src/lib/dhcpsrv/subnet_selector.h @@ -51,6 +51,15 @@ struct SubnetSelector { /// @brief Specifies if the packet is DHCP4o6 bool dhcp4o6_; + /// @brief Specifies if the packet contains address plus port information + bool address_plus_port_; + + /// @brief Specifies the packet offset for address plus port + uint8_t psid_offset_; + + /// @brief Specifies the packet psid-len for address plus port + uint8_t psid_len_; + /// @brief Default constructor. /// /// Sets the default values for the @c Selector. @@ -63,7 +72,8 @@ struct SubnetSelector { local_address_(asiolink::IOAddress("0.0.0.0")), remote_address_(asiolink::IOAddress("0.0.0.0")), client_classes_(), iface_name_(std::string()), - dhcp4o6_(false) { + dhcp4o6_(false), address_plus_port_(false), + psid_offset_(0), psid_len_(0) { } }; diff --git a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc index 46c21e5337..accbdca4ad 100644 --- a/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/tests/cql_host_data_source_unittest.cc @@ -297,6 +297,18 @@ TEST_F(CqlHostDataSourceTest, basic4HWAddr) { testBasic4(Host::IDENT_HWADDR); } +// Verifies that IPv4 host reservation with options can have a max value +// for dhcp4_subnet id +TEST_F(CqlHostDataSourceTest, maxSubnetId4) { + testMaxSubnetId4(); +} + +// Verifies that IPv6 host reservation with options can have a max value +// for dhcp6_subnet id +TEST_F(CqlHostDataSourceTest, maxSubnetId6) { + testMaxSubnetId6(); +} + // Test verifies if a host reservation can be added and later retrieved by IPv4 // address. Host uses client-id (DUID) as identifier. TEST_F(CqlHostDataSourceTest, basic4ClientId) { diff --git a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc index 74beea413a..88c6753f8e 100644 --- a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc @@ -221,7 +221,7 @@ class CqlLeaseMgrTest : public GenericLeaseMgrTest { } // This is the CQL implementation for - // GenericLeaseMgrTest::testGetExpiredLeases4(). + // GenericLeaseMgrTest::testGetExpiredLeases6(). // The GenericLeaseMgrTest implementation checks for the order of expired // leases to be from the most expired to the least expired. Cassandra // doesn't support ORDER BY without imposing a EQ / IN restriction on the @@ -283,8 +283,7 @@ class CqlLeaseMgrTest : public GenericLeaseMgrTest { } // Retrieve expired leases again. The limit of 0 means return all - // expired - // leases. + // expired leases. ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0)); // The same leases should be returned. @@ -316,7 +315,9 @@ class CqlLeaseMgrTest : public GenericLeaseMgrTest { // This the returned leases should exclude reclaimed ones. So the number // of returned leases should be roughly half of the expired leases. - ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0)); + ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0u)); + ASSERT_EQ(static_cast(saved_expired_leases.size() / 2u), + expired_leases.size()); // Make sure that returned leases are those that are not reclaimed, i.e. // those that have even index. @@ -717,12 +718,12 @@ TEST_F(CqlLeaseMgrTest, nullDuid) { testNullDuid(); } -/// @brief Tests whether memfile can store and retrieve hardware addresses +/// @brief Tests whether CQL can store and retrieve hardware addresses TEST_F(CqlLeaseMgrTest, testLease6Mac) { testLease6MAC(); } -/// @brief Tests whether memfile can store and retrieve hardware addresses +/// @brief Tests whether CQL can store and retrieve hardware addresses TEST_F(CqlLeaseMgrTest, testLease6HWTypeAndSource) { testLease6HWTypeAndSource(); } @@ -753,14 +754,14 @@ TEST_F(CqlLeaseMgrTest, recountLeaseStats6) { testRecountLeaseStats6(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. /// @todo: uncomment this once lease wipe is implemented /// for Cassandra (see #5485) TEST_F(CqlLeaseMgrTest, DISABLED_wipeLeases4) { testWipeLeases4(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. /// @todo: uncomment this once lease wipe is implemented /// for Cassandra (see #5485) TEST_F(CqlLeaseMgrTest, DISABLED_wipeLeases6) { diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index ed1208fe21..f34497aef0 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -780,7 +780,6 @@ GenericLeaseMgrTest::testBasicLease4() { detailCompareLease(leases[3], l_returned); } - void GenericLeaseMgrTest::testBasicLease6() { // Get the leases to be used for the test. @@ -1082,7 +1081,6 @@ GenericLeaseMgrTest::testGetLease4HWAddrSubnetId() { EXPECT_THROW(returned = lmptr_->getLease4(*leases[1]->hwaddr_, leases[1]->subnet_id_), isc::dhcp::MultipleRecords); - } void @@ -1343,7 +1341,6 @@ GenericLeaseMgrTest::testGetLeases6DuidSize() { // Don't bother to check DUIDs longer than the maximum - these cannot be // constructed, and that limitation is tested in the DUID/Client ID unit // tests. - } void @@ -1869,6 +1866,7 @@ GenericLeaseMgrTest::testGetExpiredLeases6() { int index = static_cast(std::distance(expired_leases.rbegin(), lease)); // Multiple current index by two, because only leases with even indexes // should have been returned. + ASSERT_LE(2 * index, leases.size()); EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_); } @@ -1883,7 +1881,6 @@ GenericLeaseMgrTest::testGetExpiredLeases6() { // Update the time of expired leases with even indexes. if (i % 2 == 0) { leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i; - } else { // Make sure remaining leases remain unexpired. leases[i]->cltt_ = current_time + 100; @@ -1901,6 +1898,7 @@ GenericLeaseMgrTest::testGetExpiredLeases6() { for (Lease6Collection::iterator lease = expired_leases.begin(); lease != expired_leases.end(); ++lease) { int index = static_cast(std::distance(expired_leases.begin(), lease)); + ASSERT_LE(2 * index, leases.size()); EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_); } @@ -1920,6 +1918,7 @@ GenericLeaseMgrTest::testGetExpiredLeases6() { for (Lease6Collection::iterator lease = expired_leases.begin(); lease != expired_leases.end(); ++lease) { int index = static_cast(std::distance(expired_leases.begin(), lease)); + ASSERT_LE(2 * index, leases.size()); EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_); } @@ -1999,6 +1998,7 @@ GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases4() { EXPECT_FALSE(lease) << "The following lease should have been" " deleted: " << leases[i]->toText(); ++should_delete_num; + } else { // If the lease is not reclaimed or it has expired less than // 15 seconds ago, the lease should still be there. @@ -2006,9 +2006,8 @@ GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases4() { " deleted: " << leases[i]->toText(); } } - - // Check that the number of leases deleted is correct. - EXPECT_EQ(deleted_num, should_delete_num); + // Check that the number of deleted leases is correct. + EXPECT_EQ(should_delete_num, deleted_num); // Make sure we can make another attempt, when there are no more leases // to be deleted. @@ -2544,7 +2543,6 @@ GenericLeaseMgrTest::testRecountLeaseStats4() { subnet->addPool(pool); cfg->add(subnet); - ASSERT_NO_THROW(CfgMgr::instance().commit()); // Create the expected stats list. At this point, the only stat @@ -2615,7 +2613,6 @@ GenericLeaseMgrTest::testRecountLeaseStats4() { ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); } - void GenericLeaseMgrTest::testRecountLeaseStats6() { using namespace stats; @@ -2652,7 +2649,6 @@ GenericLeaseMgrTest::testRecountLeaseStats6() { ASSERT_NO_THROW(CfgMgr::instance().commit()); - // Create the expected stats list. At this point, the only stat // that should be non-zero is total-nas/total-pds. for (int i = 0; i < num_subnets; ++i) { @@ -2666,7 +2662,6 @@ GenericLeaseMgrTest::testRecountLeaseStats6() { // Make sure stats are as expected. ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats)); - // Recount stats. We should have the same results. ASSERT_NO_THROW(lmptr_->recountLeaseStats4()); diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc index dcacbc403e..2cf91b2f7a 100644 --- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc @@ -495,12 +495,12 @@ TEST_F(MySqlLeaseMgrTest, nullDuid) { testNullDuid(); } -/// @brief Tests whether memfile can store and retrieve hardware addresses +/// @brief Tests whether MySQL can store and retrieve hardware addresses TEST_F(MySqlLeaseMgrTest, testLease6Mac) { testLease6MAC(); } -/// @brief Tests whether memfile can store and retrieve hardware addresses +/// @brief Tests whether MySQL can store and retrieve hardware addresses TEST_F(MySqlLeaseMgrTest, testLease6HWTypeAndSource) { testLease6HWTypeAndSource(); } @@ -531,12 +531,12 @@ TEST_F(MySqlLeaseMgrTest, recountLeaseStats6) { testRecountLeaseStats6(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases4) { testWipeLeases4(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases6) { testWipeLeases6(); } diff --git a/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc index 6f6ba77dc5..01674d7bb9 100644 --- a/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc @@ -598,7 +598,7 @@ TEST_F(PgSqlBasicsTest, smallIntTest) { ints.push_back(-1); ints.push_back(0); ints.push_back(0x7fff); - ints.push_back(0xffff); + ints.push_back(static_cast(0xffff)); // Insert a row for each reference value PsqlBindArrayPtr bind_array; diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc index f0b3ec7b47..d5e5d074f0 100644 --- a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc @@ -129,10 +129,10 @@ TEST(PgSqlOpenTest, OpenDatabase) { // Check that lease manager open the database opens correctly with a longer // timeout. If it fails, print the error message. try { - string connection_string = validPgSQLConnectionString() + string(" ") + - string(VALID_TIMEOUT); + string connection_string = + validPgSQLConnectionString() + string(" ") + string(VALID_TIMEOUT); LeaseMgrFactory::create(connection_string); - EXPECT_NO_THROW((void) LeaseMgrFactory::instance()); + EXPECT_NO_THROW((void)LeaseMgrFactory::instance()); LeaseMgrFactory::destroy(); } catch (const isc::Exception& ex) { FAIL() << "*** ERROR: unable to open database, reason:\n" @@ -148,33 +148,41 @@ TEST(PgSqlOpenTest, OpenDatabase) { // Check that wrong specification of backend throws an exception. // (This is really a check on LeaseMgrFactory, but is convenient to // perform here.) - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)), + EXPECT_THROW( + LeaseMgrFactory::create(connectionString(NULL, VALID_NAME, VALID_HOST, + INVALID_USER, VALID_PASSWORD)), InvalidParameter); - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)), + EXPECT_THROW( + LeaseMgrFactory::create(connectionString( + INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)), InvalidType); // Check that invalid login data causes an exception. - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)), - DbOpenError); + EXPECT_THROW(LeaseMgrFactory::create( + connectionString(PGSQL_VALID_TYPE, INVALID_NAME, + VALID_HOST, VALID_USER, VALID_PASSWORD)), + DbOpenError); EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)), - DbOpenError); + PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, + VALID_PASSWORD)), + DbOpenError); - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)), - DbOpenError); + EXPECT_THROW(LeaseMgrFactory::create( + connectionString(PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, + INVALID_USER, VALID_PASSWORD)), + DbOpenError); - // This test might fail if 'auth-method' in PostgresSQL host-based authentication + // This test might fail if 'auth-method' in PostgresSQL host-based + // authentication // file (/var/lib/pgsql/9.4/data/pg_hba.conf) is set to 'trust', - // which allows logging without password. 'Auth-method' should be changed to 'password'. - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)), - DbOpenError); + // which allows logging without password. 'Auth-method' should be changed to + // 'password'. + EXPECT_THROW(LeaseMgrFactory::create( + connectionString(PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, + VALID_USER, INVALID_PASSWORD)), + DbOpenError); // Check for invalid timeouts EXPECT_THROW(LeaseMgrFactory::create(connectionString( @@ -182,12 +190,14 @@ TEST(PgSqlOpenTest, OpenDatabase) { DbInvalidTimeout); EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)), - DbInvalidTimeout); + PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, + VALID_PASSWORD, INVALID_TIMEOUT_2)), + DbInvalidTimeout); // Check for missing parameters - EXPECT_THROW(LeaseMgrFactory::create(connectionString( - PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)), + EXPECT_THROW( + LeaseMgrFactory::create(connectionString( + PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)), NoDatabaseName); // Tidy up after the test @@ -484,12 +494,12 @@ TEST_F(PgSqlLeaseMgrTest, nullDuid) { testNullDuid(); } -/// @brief Tests whether Postgres can store and retrieve hardware addresses +/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses TEST_F(PgSqlLeaseMgrTest, testLease6Mac) { testLease6MAC(); } -/// @brief Tests whether Postgres can store and retrieve hardware addresses +/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses TEST_F(PgSqlLeaseMgrTest, testLease6HWTypeAndSource) { testLease6HWTypeAndSource(); } @@ -520,12 +530,12 @@ TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6) { testRecountLeaseStats6(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases4) { testWipeLeases4(); } -// @brief Tests that leases from specific subnet can be removed. +/// @brief Tests that leases from specific subnet can be removed. TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases6) { testWipeLeases6(); } diff --git a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc index 4842fbd37d..160b825a5d 100644 --- a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc @@ -141,6 +141,8 @@ class SharedNetwork4ParserTest : public SharedNetworkParserTest { " \"4o6-interface-id\": \"\"," " \"4o6-subnet\": \"\"," " \"dhcp4o6-port\": 0," + " \"v4-psid-offset\": 0," + " \"v4-psid-len\": 0," " \"decline-probation-period\": 86400," " \"reservation-mode\": \"all\"" " }," @@ -162,6 +164,8 @@ class SharedNetwork4ParserTest : public SharedNetworkParserTest { " \"4o6-interface-id\": \"\"," " \"4o6-subnet\": \"\"," " \"dhcp4o6-port\": 0," + " \"v4-psid-offset\": 0," + " \"v4-psid-len\": 0," " \"decline-probation-period\": 86400," " \"reservation-mode\": \"all\"" " }" diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc index 36a37efb48..41be4192a0 100644 --- a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc +++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc @@ -31,6 +31,12 @@ Dhcp4o6TestIpc::open() { } } +void +Dhcp4o6TestIpc::close() { + // Use the base IPC to close the socket + Dhcp4o6IpcBase::close(); +} + void Dhcp4o6TestIpc::receiveHandler() { pkt_received_ = receive(); diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h index 5109de06c3..6af2295395 100644 --- a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h +++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h @@ -43,6 +43,9 @@ class Dhcp4o6TestIpc : public Dhcp4o6IpcBase { /// over the socket. virtual void open(); + /// @brief Close the IPC socket. + virtual void close(); + /// @brief Retrieve port which socket is bound to. uint16_t getPort() const { return (port_); diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index 2e7ef08ad8..65457591b3 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -290,7 +290,8 @@ GenericHostDataSourceTest::testMaxSubnetId4() { EXPECT_FALSE(host_by_id); } -void GenericHostDataSourceTest::testMaxSubnetId6() { +void +GenericHostDataSourceTest::testMaxSubnetId6() { std::vector ident; ident = HostDataSourceUtils::generateIdentifier(); diff --git a/src/share/database/scripts/cql/.gitignore b/src/share/database/scripts/cql/.gitignore index d184e0fe31..9976ce894b 100644 --- a/src/share/database/scripts/cql/.gitignore +++ b/src/share/database/scripts/cql/.gitignore @@ -1,2 +1 @@ upgrade_1.0_to_2.0.sh - diff --git a/src/share/database/scripts/cql/Makefile.am b/src/share/database/scripts/cql/Makefile.am index 46f8e26f8e..68e7be4c3e 100644 --- a/src/share/database/scripts/cql/Makefile.am +++ b/src/share/database/scripts/cql/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . sqlscriptsdir = ${datarootdir}/${PACKAGE_NAME}/scripts/cql -sqlscripts_DATA = dhcpdb_create.cql +sqlscripts_DATA = dhcpdb_create.cql sqlscripts_DATA += dhcpdb_drop.cql sqlscripts_DATA += upgrade_1.0_to_2.0.sh sqlscripts_DATA += soft_wipe.cql diff --git a/src/share/database/scripts/cql/dhcpdb_create.cql b/src/share/database/scripts/cql/dhcpdb_create.cql index be163e68cd..b358698528 100644 --- a/src/share/database/scripts/cql/dhcpdb_create.cql +++ b/src/share/database/scripts/cql/dhcpdb_create.cql @@ -45,7 +45,7 @@ -- Table `lease4` -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS lease4 ( - address int, + address bigint, hwaddr blob, client_id blob, valid_lifetime bigint, diff --git a/src/share/database/scripts/mysql/.gitignore b/src/share/database/scripts/mysql/.gitignore index 8f9f1ce2bc..6f2212231e 100644 --- a/src/share/database/scripts/mysql/.gitignore +++ b/src/share/database/scripts/mysql/.gitignore @@ -1,8 +1,9 @@ -/upgrade_1.0_to_2.0.sh -/upgrade_2.0_to_3.0.sh -/upgrade_3.0_to_4.0.sh -/upgrade_4.0_to_4.1.sh -/upgrade_4.1_to_5.0.sh -/upgrade_5.0_to_5.1.sh -/upgrade_5.1_to_5.2.sh -/upgrade_5.2_to_6.0.sh +upgrade_1.0_to_2.0.sh +upgrade_2.0_to_3.0.sh +upgrade_3.0_to_4.0.sh +upgrade_4.0_to_4.1.sh +upgrade_4.1_to_5.0.sh +upgrade_5.0_to_5.1.sh +upgrade_5.1_to_5.2.sh +upgrade_5.2_to_6.0.sh +upgrade_6.0_to_7.0.sh diff --git a/src/share/database/scripts/mysql/Makefile.am b/src/share/database/scripts/mysql/Makefile.am index eff6631399..6fcdfadf5a 100644 --- a/src/share/database/scripts/mysql/Makefile.am +++ b/src/share/database/scripts/mysql/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = . sqlscriptsdir = ${datarootdir}/${PACKAGE_NAME}/scripts/mysql -sqlscripts_DATA = dhcpdb_create.mysql +sqlscripts_DATA = dhcpdb_create.mysql sqlscripts_DATA += dhcpdb_drop.mysql sqlscripts_DATA += upgrade_1.0_to_2.0.sh sqlscripts_DATA += upgrade_2.0_to_3.0.sh @@ -11,6 +11,7 @@ sqlscripts_DATA += upgrade_4.1_to_5.0.sh sqlscripts_DATA += upgrade_5.0_to_5.1.sh sqlscripts_DATA += upgrade_5.1_to_5.2.sh sqlscripts_DATA += upgrade_5.2_to_6.0.sh +sqlscripts_DATA += upgrade_6.0_to_7.0.sh DISTCLEANFILES = upgrade_1.0_to_2.0.sh DISTCLEANFILES += upgrade_2.0_to_3.0.sh @@ -20,5 +21,6 @@ DISTCLEANFILES += upgrade_4.1_to_5.0.sh DISTCLEANFILES += upgrade_5.0_to_5.1.sh DISTCLEANFILES += upgrade_5.1_to_5.2.sh DISTCLEANFILES += upgrade_5.2_to_6.0.sh +DISTCLEANFILES += upgrade_6.0_to_7.0.sh EXTRA_DIST = ${sqlscripts_DATA} diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql index 09dd299b0a..4d5c3b1a38 100644 --- a/src/share/database/scripts/mysql/dhcpdb_create.mysql +++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql @@ -43,7 +43,6 @@ CREATE TABLE lease4 ( hostname VARCHAR(255) # The FQDN of the client ) ENGINE = INNODB; - # Create search indexes for lease4 table # index by hwaddr and subnet_id CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id); @@ -677,6 +676,13 @@ SET version = '6', minor = '0'; # This line concludes database upgrade to version 6.0. +ALTER TABLE lease4 MODIFY address BIGINT UNSIGNED NOT NULL; + +# Update the schema version number +UPDATE schema_version +SET version = '7', minor = '0'; +# This line concludes database upgrade to version 7.0. + # Notes: # # Indexes diff --git a/src/share/database/scripts/mysql/upgrade_6.0_to_7.0.sh.in b/src/share/database/scripts/mysql/upgrade_6.0_to_7.0.sh.in new file mode 100644 index 0000000000..4fac437706 --- /dev/null +++ b/src/share/database/scripts/mysql/upgrade_6.0_to_7.0.sh.in @@ -0,0 +1,31 @@ +#!/bin/sh + +# Include utilities. Use installed version if available and +# use build version if it isn't. +if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then + . @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh +else + . @abs_top_builddir@/src/bin/admin/admin-utils.sh +fi + +VERSION=`mysql_version "$@"` + +if [ "$VERSION" != "6.0" ]; then + printf "This script upgrades 6.0 to 7.0. Reported version is $VERSION. Skipping upgrade.\n" + exit 0 +fi + +mysql "$@" <