diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 2f7b76784f..435407d225 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -3177,13 +3177,25 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) { // Get a lease. Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx); - // Tracks whether or not the client name (FQDN or host) has changed since - // the lease was allocated. - bool client_name_changed = false; + bool reprocess_client_name = false; + if (lease) { + auto ddns_params = ex.getContext()->getDdnsParams(); + auto pool = ddns_params->setPoolFromAddress(lease->addr_); + if (pool) { + reprocess_client_name = pool->hasDdnsParameters(); + } + } // Subnet may be modified by the allocation engine, if the initial subnet // belongs to a shared network. if (subnet && ctx->subnet_ && subnet->getID() != ctx->subnet_->getID()) { + // We changed subnets and that means DDNS parameters might be different + // so we need to rerun client name processing logic. Arguably we could + // compare DDNS parameters for both subnets and then decide if we need + // to rerun the name logic, but that's not likely to be any faster than + // just re-running the name logic. @todo When inherited parameter + // performance is improved this argument could be revisited. + // Another case is the new subnet has a reserved hostname. SharedNetwork4Ptr network; subnet->getSharedNetwork(network); LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_DYNAMICALLY_CHANGED) @@ -3193,37 +3205,36 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) { .arg(network ? network->getName() : ""); subnet = ctx->subnet_; - if (lease) { - // We changed subnets and that means DDNS parameters might be different - // so we need to rerun client name processing logic. Arguably we could - // compare DDNS parameters for both subnets and then decide if we need - // to rerun the name logic, but that's not likely to be any faster than - // just re-running the name logic. @todo When inherited parameter - // performance is improved this argument could be revisited. - // Another case is the new subnet has a reserved hostname. + reprocess_client_name = true; + } + } - // First, we need to remove the prior values from the response and reset - // those in context, to give processClientName a clean slate. - resp->delOption(DHO_FQDN); - resp->delOption(DHO_HOST_NAME); - ctx->hostname_ = ""; - ctx->fwd_dns_update_ = false; - ctx->rev_dns_update_ = false; + // Tracks whether or not the client name (FQDN or host) has changed since + // the lease was allocated. + bool client_name_changed = false; - // Regenerate the name and dns flags. - processClientName(ex); + if (reprocess_client_name) { + // First, we need to remove the prior values from the response and reset + // those in context, to give processClientName a clean slate. + resp->delOption(DHO_FQDN); + resp->delOption(DHO_HOST_NAME); + ctx->hostname_ = ""; + ctx->fwd_dns_update_ = false; + ctx->rev_dns_update_ = false; - // If the results are different from the values already on the - // lease, flag it so the lease gets updated down below. - if ((lease->hostname_ != ctx->hostname_) || - (lease->fqdn_fwd_ != ctx->fwd_dns_update_) || - (lease->fqdn_rev_ != ctx->rev_dns_update_)) { - lease->hostname_ = ctx->hostname_; - lease->fqdn_fwd_ = ctx->fwd_dns_update_; - lease->fqdn_rev_ = ctx->rev_dns_update_; - client_name_changed = true; - } + // Regenerate the name and dns flags. + processClientName(ex); + + // If the results are different from the values already on the + // lease, flag it so the lease gets updated down below. + if ((lease->hostname_ != ctx->hostname_) || + (lease->fqdn_fwd_ != ctx->fwd_dns_update_) || + (lease->fqdn_rev_ != ctx->rev_dns_update_)) { + lease->hostname_ = ctx->hostname_; + lease->fqdn_fwd_ = ctx->fwd_dns_update_; + lease->fqdn_rev_ = ctx->rev_dns_update_; + client_name_changed = true; } } diff --git a/src/lib/dhcpsrv/ncr_generator.cc b/src/lib/dhcpsrv/ncr_generator.cc index 61540026e5..978d7dcc9c 100644 --- a/src/lib/dhcpsrv/ncr_generator.cc +++ b/src/lib/dhcpsrv/ncr_generator.cc @@ -39,7 +39,7 @@ namespace { template void queueNCRCommon(const NameChangeType& chg_type, const LeasePtrType& lease, const IdentifierType& identifier, const std::string& label, - NetworkPtr subnet) { + const ConstSubnetPtr subnet) { // Check if there is a need for update. if (lease->hostname_.empty() || (!lease->fqdn_fwd_ && !lease->fqdn_rev_) || !CfgMgr::instance().getD2ClientMgr().ddnsEnabled()) { @@ -51,22 +51,26 @@ void queueNCRCommon(const NameChangeType& chg_type, const LeasePtrType& lease, return; } - ConflictResolutionMode conflict_resolution_mode = CHECK_WITH_DHCID; - util::Optional ddns_ttl_percent; - util::Optional ddns_ttl; - util::Optional ddns_ttl_min; - util::Optional ddns_ttl_max; - if (subnet) { - auto mode = subnet->getDdnsConflictResolutionMode(); - if (!mode.empty()) { - conflict_resolution_mode = StringToConflictResolutionMode(mode); - } + ConflictResolutionMode conflict_resolution_mode = CHECK_WITH_DHCID; + util::Optional ddns_ttl_percent; + util::Optional ddns_ttl; + util::Optional ddns_ttl_min; + util::Optional ddns_ttl_max; + if (subnet) { + // Create a DdnsParams so we have access to pool scope values. + DdnsParams ddns_params(subnet, true); + static_cast(ddns_params.setPoolFromAddress(lease->addr_)); - ddns_ttl_percent = subnet->getDdnsTtlPercent(); - ddns_ttl = subnet->getDdnsTtl(); - ddns_ttl_min = subnet->getDdnsTtlMin(); - ddns_ttl_max = subnet->getDdnsTtlMax(); - } + auto mode = ddns_params.getConflictResolutionMode(); + if (!mode.empty()) { + conflict_resolution_mode = StringToConflictResolutionMode(mode); + } + + ddns_ttl_percent = ddns_params.getTtlPercent(); + ddns_ttl = ddns_params.getTtl(); + ddns_ttl_min = ddns_params.getTtlMin(); + ddns_ttl_max = ddns_params.getTtlMax(); + } try { // Create DHCID @@ -112,8 +116,8 @@ void queueNCR(const NameChangeType& chg_type, const Lease4Ptr& lease) { if (lease) { // Figure out from the lease's subnet if we should use conflict resolution. // If there's no subnet, something hinky is going on so we'll set it true. - Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg() - ->getCfgSubnets4()->getSubnet(lease->subnet_id_); + ConstSubnet4Ptr subnet = CfgMgr::instance().getCurrentCfg() + ->getCfgSubnets4()->getSubnet(lease->subnet_id_); // Client id takes precedence over HW address. if (lease->client_id_) { @@ -133,7 +137,7 @@ void queueNCR(const NameChangeType& chg_type, const Lease6Ptr& lease) { if (lease && (lease->type_ != Lease::TYPE_PD) && lease->duid_) { // Figure out from the lease's subnet if we should use conflict resolution. // If there's no subnet, something hinky is going on so we'll set it true. - Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg() + ConstSubnet6Ptr subnet = CfgMgr::instance().getCurrentCfg() ->getCfgSubnets6()->getSubnet(lease->subnet_id_); queueNCRCommon(chg_type, lease, *(lease->duid_), Pkt6::makeLabel(lease->duid_, lease->hwaddr_), subnet); diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index 69af817f3f..c60470e6b7 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -834,8 +834,7 @@ public: ddns_update_on_renew_ = ddns_update_on_renew; } - - /// @brief Returns ib-ddns-conflict-resolution-mode + /// @brief Returns ddns-conflict-resolution-mode /// /// @param inheritance inheritance mode to be used. util::Optional @@ -846,7 +845,7 @@ public: CfgGlobals::DDNS_CONFLICT_RESOLUTION_MODE)); } - /// @brief Sets new ib-ddns-conflict-resolution-mode + /// @brief Sets new ddns-conflict-resolution-mode /// /// @param ddns_conflict_resolution_mode New value to use. void setDdnsConflictResolutionMode(const util::Optional& ddns_conflict_resolution_mode) { diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index 2c80084e6e..904ee30eb5 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -56,6 +56,24 @@ Pool::toText() const { return (tmp.str()); } +bool +Pool::hasDdnsParameters() { + return (!(ddns_send_updates_.unspecified() && + ddns_override_no_update_.unspecified() && + ddns_override_client_update_.unspecified() && + ddns_replace_client_name_mode_.unspecified() && + ddns_generated_prefix_.unspecified() && + ddns_qualifying_suffix_.unspecified() && + ddns_update_on_renew_.unspecified() && + ddns_conflict_resolution_mode_.unspecified() && + ddns_ttl_percent_.unspecified() && + ddns_ttl_.unspecified() && + ddns_ttl_min_.unspecified() && + ddns_ttl_max_.unspecified() && + hostname_char_set_.unspecified() && + hostname_char_replacement_.unspecified())); +} + Pool4::Pool4(const isc::asiolink::IOAddress& first, const isc::asiolink::IOAddress& last) : Pool(Lease::TYPE_V4, first, last) { @@ -142,6 +160,67 @@ Pool::toElement() const { map->set("pool-id", Element::create(static_cast(id_))); } + // Add in DDNS paramters for non-prefix pools. + if (type_ != Lease::TYPE_PD) { + if (!ddns_send_updates_.unspecified()) { + map->set("ddns-send-updates", Element::create(ddns_send_updates_)); + } + + if (!ddns_override_no_update_.unspecified()) { + map->set("ddns-override-no-update", Element::create(ddns_override_no_update_)); + } + + if (!ddns_override_client_update_.unspecified()) { + map->set("ddns-override-client-update", Element::create(ddns_override_client_update_)); + } + + if (!ddns_replace_client_name_mode_.unspecified()) { + map->set("ddns-replace-client-name", + Element::create(D2ClientConfig:: + replaceClientNameModeToString(ddns_replace_client_name_mode_))); + } + + if (!ddns_generated_prefix_.unspecified()) { + map->set("ddns-generated-prefix", Element::create(ddns_generated_prefix_)); + } + + if (!ddns_qualifying_suffix_.unspecified()) { + map->set("ddns-qualifying-suffix", Element::create(ddns_qualifying_suffix_)); + } + + if (!ddns_update_on_renew_.unspecified()) { + map->set("ddns-update-on-renew", Element::create(ddns_update_on_renew_)); + } + + if (!ddns_conflict_resolution_mode_.unspecified()) { + map->set("ddns-conflict-resolution-mode", Element::create(ddns_conflict_resolution_mode_)); + } + + if (!ddns_ttl_percent_.unspecified()) { + map->set("ddns-ttl-percent", Element::create(ddns_ttl_percent_)); + } + + if (!ddns_ttl_.unspecified()) { + map->set("ddns-ttl", Element::create(ddns_ttl_)); + } + + if (!ddns_ttl_min_.unspecified()) { + map->set("ddns-ttl-min", Element::create(ddns_ttl_min_)); + } + + if (!ddns_ttl_max_.unspecified()) { + map->set("ddns-ttl-max", Element::create(ddns_ttl_max_)); + } + + if (!hostname_char_set_.unspecified()) { + map->set("hostname-char-set", Element::create(hostname_char_set_)); + } + + if (!hostname_char_replacement_.unspecified()) { + map->set("hostname-char-replacement", Element::create(hostname_char_replacement_)); + } + } + return (map); } diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h index 7d2ad92090..a6286a4ae6 100644 --- a/src/lib/dhcpsrv/pool.h +++ b/src/lib/dhcpsrv/pool.h @@ -14,9 +14,11 @@ #include #include #include +#include #include #include #include +#include #include @@ -169,6 +171,199 @@ public: /// @return A pointer to unparsed pool configuration. virtual data::ElementPtr toElement() const; + + /// @brief Returns ddns-send-updates + util::Optional + getDdnsSendUpdates() const { + return (ddns_send_updates_); + } + + /// @brief Sets new ddns-send-updates + /// + /// @param ddns_send_updates New value to use. + void setDdnsSendUpdates(const util::Optional& ddns_send_updates) { + ddns_send_updates_ = ddns_send_updates; + } + + /// @brief Returns ddns-override-no-update + util::Optional + getDdnsOverrideNoUpdate() const { + return (ddns_override_no_update_); + } + + /// @brief Sets new ddns-override-no-update + /// + /// @param ddns_override_no_update New value to use. + void setDdnsOverrideNoUpdate(const util::Optional& ddns_override_no_update) { + ddns_override_no_update_ = ddns_override_no_update; + } + + /// @brief Returns ddns-override-client-update + util::Optional + getDdnsOverrideClientUpdate() const { + return (ddns_override_client_update_); + } + + /// @brief Sets new ddns-override-client-update + /// + /// @param ddns_override_client_update New value to use. + void setDdnsOverrideClientUpdate(const util::Optional& + ddns_override_client_update) { + ddns_override_client_update_ = ddns_override_client_update; + } + + /// @brief Returns ddns-replace-client-name-mode + util::Optional + getDdnsReplaceClientNameMode() const { + return (ddns_replace_client_name_mode_); + } + + /// @brief Sets new ddns-replace-client-name-mode + /// + /// @param ddns_replace_client_name_mode New value to use. + void + setDdnsReplaceClientNameMode(const util::Optional& + ddns_replace_client_name_mode) { + ddns_replace_client_name_mode_ = ddns_replace_client_name_mode; + } + + /// @brief Returns ddns-generated-prefix + util::Optional + getDdnsGeneratedPrefix() const { + return (ddns_generated_prefix_); + } + + /// @brief Sets new ddns-generated-prefix + /// + /// @param ddns_generated_prefix New value to use. + void setDdnsGeneratedPrefix(const util::Optional& ddns_generated_prefix) { + ddns_generated_prefix_ = ddns_generated_prefix; + } + + /// @brief Returns ddns-qualifying-suffix + util::Optional + getDdnsQualifyingSuffix() const { + return (ddns_qualifying_suffix_); + } + + /// @brief Sets new ddns-qualifying-suffix + /// + /// @param ddns_qualifying_suffix New value to use. + void setDdnsQualifyingSuffix(const util::Optional& ddns_qualifying_suffix) { + ddns_qualifying_suffix_ = ddns_qualifying_suffix; + } + + /// @brief Returns ddns-update-on-renew + util::Optional + getDdnsUpdateOnRenew() const { + return (ddns_update_on_renew_); + } + + /// @brief Sets new ddns-update-on-renew + /// + /// @param ddns_update_on_renew New value to use. + void setDdnsUpdateOnRenew(const util::Optional& ddns_update_on_renew) { + ddns_update_on_renew_ = ddns_update_on_renew; + } + + /// @brief Returns ddns-conflict-resolution-mode + util::Optional + getDdnsConflictResolutionMode() const { + return (ddns_conflict_resolution_mode_); + } + + /// @brief Sets new ddns-conflict-resolution-mode + /// + /// @param ddns_conflict_resolution_mode New value to use. + void setDdnsConflictResolutionMode(const util::Optional& ddns_conflict_resolution_mode) { + ddns_conflict_resolution_mode_ = ddns_conflict_resolution_mode; + } + + + /// @brief Returns ddns-ttl-percent + util::Optional + getDdnsTtlPercent() const { + return (ddns_ttl_percent_); + } + + /// @brief Sets new ddns-ttl-percent + /// + /// @param ddns_ttl_percent New value to use. + void setDdnsTtlPercent(const util::Optional& ddns_ttl_percent) { + ddns_ttl_percent_ = ddns_ttl_percent; + } + + /// @brief Returns ddns-ttl + util::Optional + getDdnsTtl() const { + return (ddns_ttl_); + } + + /// @brief Sets new ddns-ttl + /// + /// @param ddns_ttl New value to use. + void setDdnsTtl(const util::Optional& ddns_ttl) { + ddns_ttl_ = ddns_ttl; + } + + /// @brief Returns ddns-ttl-min + util::Optional + getDdnsTtlMin() const { + return (ddns_ttl_min_); + } + + /// @brief Sets new ddns-ttl-min + /// + /// @param ddns_ttl_min New value to use. + void setDdnsTtlMin(const util::Optional& ddns_ttl_min) { + ddns_ttl_min_ = ddns_ttl_min; + } + + /// @brief Returns ddns-ttl-max + util::Optional + getDdnsTtlMax() const { + return (ddns_ttl_max_); + } + + /// @brief Sets new ddns-ttl-max + /// + /// @param ddns_ttl_max New value to use. + void setDdnsTtlMax(const util::Optional& ddns_ttl_max) { + ddns_ttl_max_ = ddns_ttl_max; + } + + /// @brief Return the char set regexp used to sanitize client hostnames. + util::Optional + getHostnameCharSet() const { + return (hostname_char_set_); + } + + /// @brief Sets new hostname-char-set + /// + /// @param hostname_char_set New value to use. + void setHostnameCharSet(const util::Optional& hostname_char_set) { + hostname_char_set_ = hostname_char_set; + } + + /// @brief Return the invalid char replacement used to sanitize client hostnames. + util::Optional + getHostnameCharReplacement() const { + return (hostname_char_replacement_); + } + + /// @brief Sets new hostname-char-replacement + /// + /// @param hostname_char_replacement New value to use. + void setHostnameCharReplacement(const util::Optional& + hostname_char_replacement) { + hostname_char_replacement_ = hostname_char_replacement; + } + + /// @brief Checks if any of the DDNS parameters has a value. + /// + /// @return True if any of the DDNS parameters are specified. + bool hasDdnsParameters(); + protected: /// @brief protected constructor @@ -228,6 +423,52 @@ protected: /// @brief Holds pool-specific allocation state. AllocationStatePtr allocation_state_; + + /// @brief Should Kea perform DNS updates. Used to provide scoped enabling + /// and disabling of updates. + util::Optional ddns_send_updates_; + + /// @brief Should Kea perform updates, even if client requested no updates. + /// Overrides the client request for no updates via the N flag. + util::Optional ddns_override_no_update_; + + /// @brief Should Kea perform updates, even if client requested delegation. + util::Optional ddns_override_client_update_; + + /// @brief How Kea should handle the domain-name supplied by the client. + util::Optional ddns_replace_client_name_mode_; + + /// @brief Prefix Kea should use when generating domain-names. + util::Optional ddns_generated_prefix_; + + /// @brief Suffix Kea should use when to qualify partial domain-names. + util::Optional ddns_qualifying_suffix_; + + /// @brief Should Kea perform updates when leases are extended + util::Optional ddns_update_on_renew_; + + /// @brief DDNS conflict resolution mode + util::Optional ddns_conflict_resolution_mode_; + + /// @brief Percentage of the lease lifetime to use for DNS TTL. + util::Optional ddns_ttl_percent_; + + /// @brief Explicit value to use for DNS TTL. + util::Optional ddns_ttl_; + + /// @brief Minimum value to use for DNS TTL. + util::Optional ddns_ttl_min_; + + /// @brief Maximum value to use for DNS TTL. + util::Optional ddns_ttl_max_; + + /// @brief Regular expression describing invalid characters for client + /// hostnames. + util::Optional hostname_char_set_; + + /// @brief A string to replace invalid characters when scrubbing hostnames. + /// Meaningful only if hostname_char_set_ is not empty. + util::Optional hostname_char_replacement_; }; class Pool4; diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index bbb7d37eeb..24defcf9a8 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -977,6 +977,13 @@ DdnsParams::getEnableUpdates() const { return (false); } + if (pool_) { + auto optional = pool_->getDdnsSendUpdates(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (d2_client_enabled_ && subnet_->getDdnsSendUpdates().get()); } @@ -986,6 +993,13 @@ DdnsParams::getOverrideNoUpdate() const { return (false); } + if (pool_) { + auto optional = pool_->getDdnsOverrideNoUpdate(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsOverrideNoUpdate().get()); } @@ -994,6 +1008,13 @@ bool DdnsParams::getOverrideClientUpdate() const { return (false); } + if (pool_) { + auto optional = pool_->getDdnsOverrideClientUpdate(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsOverrideClientUpdate().get()); } @@ -1003,6 +1024,13 @@ DdnsParams::getReplaceClientNameMode() const { return (D2ClientConfig::RCM_NEVER); } + if (pool_) { + auto optional = pool_->getDdnsReplaceClientNameMode(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsReplaceClientNameMode().get()); } @@ -1012,6 +1040,13 @@ DdnsParams::getGeneratedPrefix() const { return (""); } + if (pool_) { + auto optional = pool_->getDdnsGeneratedPrefix(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsGeneratedPrefix().get()); } @@ -1021,6 +1056,13 @@ DdnsParams::getQualifyingSuffix() const { return (""); } + if (pool_) { + auto optional = pool_->getDdnsQualifyingSuffix(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsQualifyingSuffix().get()); } @@ -1103,15 +1145,46 @@ DdnsParams::getUpdateOnRenew() const { return (false); } + if (pool_) { + auto optional = pool_->getDdnsUpdateOnRenew(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsUpdateOnRenew().get()); } +std::string +DdnsParams::getConflictResolutionMode() const { + if (!subnet_) { + return ("check-with-dhcid"); + } + + if (pool_) { + auto optional = pool_->getDdnsConflictResolutionMode(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + + return (subnet_->getDdnsConflictResolutionMode().get()); +} + + util::Optional DdnsParams::getTtlPercent() const { if (!subnet_) { return (util::Optional()); } + if (pool_) { + auto optional = pool_->getDdnsTtlPercent(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsTtlPercent()); } @@ -1121,6 +1194,13 @@ DdnsParams::getTtl() const { return (util::Optional()); } + if (pool_) { + auto optional = pool_->getDdnsTtl(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsTtl()); } @@ -1130,6 +1210,13 @@ DdnsParams::getTtlMin() const { return (util::Optional()); } + if (pool_) { + auto optional = pool_->getDdnsTtlMin(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsTtlMin()); } @@ -1139,17 +1226,28 @@ DdnsParams::getTtlMax() const { return (util::Optional()); } + if (pool_) { + auto optional = pool_->getDdnsTtlMax(); + if (!optional.unspecified()) { + return (optional.get()); + } + } + return (subnet_->getDdnsTtlMax()); } -std::string -DdnsParams::getConflictResolutionMode() const { +PoolPtr +DdnsParams::setPoolFromAddress(const asiolink::IOAddress& address) { if (!subnet_) { - return ("check-with-dhcid"); + /// @todo Not sure this can happen. + isc_throw(InvalidOperation, + "DdnsParams::setPoolFromAddress called without a subnet"); } - return (subnet_->getDdnsConflictResolutionMode().get()); + pool_ = subnet_->getPool((address.isV4() ? Lease::TYPE_V4 : Lease::TYPE_NA), address, false); + return (pool_); } + } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index 53c275f6d7..4913e1c60b 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -51,26 +51,15 @@ public: /// @brief Default constructor DdnsParams() : subnet_(), d2_client_enabled_(false) {} - /// @brief Constructor for DHCPv4 subnets + /// @brief Constructor /// - /// @param subnet Pointer to Subnet4 instance to use for fetching + /// @param subnet Pointer to subnet instance to use for fetching /// parameter values (typically this is the selected subnet). /// @param d2_client_enabled flag which indicates whether or not /// D2Client is enabled (typically the value should come from /// global D2Client configuration). - DdnsParams(const ConstSubnet4Ptr& subnet, bool d2_client_enabled) - : subnet_(boost::dynamic_pointer_cast(subnet)), - d2_client_enabled_(d2_client_enabled) {} - - /// @brief Constructor for DHCPv6 subnets - /// - /// @param subnet Pointer to Subnet6 instance to use for fetching - /// parameter values (typically this is the selected subnet). - /// @param d2_client_enabled flag which indicates whether or not - /// D2Client is enabled (typically the value should come from - /// global D2Client configuration). - DdnsParams(const ConstSubnet6Ptr& subnet, bool d2_client_enabled) - : subnet_(boost::dynamic_pointer_cast(subnet)), + DdnsParams(const ConstSubnetPtr& subnet, bool d2_client_enabled) + : subnet_(subnet), d2_client_enabled_(d2_client_enabled) {} /// @brief Returns whether or not DHCP DDNS updating is enabled. @@ -192,12 +181,25 @@ public: } } + PoolPtr setPoolFromAddress(const asiolink::IOAddress& address); + + void resetPool() { + pool_.reset(); + } + + PoolPtr getPool() const { + return (pool_); + } + private: /// @brief Subnet from which values should be fetched. ConstSubnetPtr subnet_; /// @brief Flag indicating whether or not the D2Client is enabled. bool d2_client_enabled_; + + /// @brief Pool within the subnet from which values should be fetched. + PoolPtr pool_; }; /// @brief Defines a pointer for DdnsParams instances. diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc index a3424a3fd9..044afbffed 100644 --- a/src/lib/dhcpsrv/tests/pool_unittest.cc +++ b/src/lib/dhcpsrv/tests/pool_unittest.cc @@ -25,6 +25,131 @@ using namespace isc::asiolink; namespace { +/// @brief Class for testing pools. +class PoolTest : public ::testing::Test { +public: + /// @brief Constructor + PoolTest() = default; + + /// @brief Destructor + virtual ~PoolTest() = default; + + /// @brief Verifies the DDNS parameter accessors and the + /// hasDdnsParameters() method. + /// + /// @param family sets the protocol to be used AF_INET or AF_INET6. + void checkDdnsParamters(uint16_t family) { + PoolPtr pool; + if (family == AF_INET) { + pool.reset(new Pool4(IOAddress("192.0.2.0"), 25)); + } else { + pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8::1"), + IOAddress("2001:db8::2"))); + } + + EXPECT_FALSE(pool->hasDdnsParameters()); + + util::Optional bool_unspec; + util::Optional bool_spec(true); + + pool->setDdnsSendUpdates(bool_spec); + EXPECT_EQ(pool->getDdnsSendUpdates(), bool_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsSendUpdates(bool_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsOverrideNoUpdate(bool_spec); + EXPECT_EQ(pool->getDdnsOverrideNoUpdate(), bool_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsOverrideNoUpdate(bool_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsOverrideClientUpdate(bool_spec); + EXPECT_EQ(pool->getDdnsOverrideClientUpdate(), bool_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsOverrideClientUpdate(bool_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + util::Optional mode_unspec; + util::Optional + mode_spec(D2ClientConfig::RCM_WHEN_PRESENT); + + pool->setDdnsReplaceClientNameMode(mode_spec); + EXPECT_EQ(pool->getDdnsReplaceClientNameMode(), mode_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsReplaceClientNameMode(mode_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + util::Optional string_unspec; + util::Optional string_spec("some_string"); + + pool->setDdnsGeneratedPrefix(string_spec); + EXPECT_EQ(pool->getDdnsGeneratedPrefix(), string_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsGeneratedPrefix(string_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsQualifyingSuffix(string_spec); + EXPECT_EQ(pool->getDdnsQualifyingSuffix(), string_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsQualifyingSuffix(string_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsUpdateOnRenew(bool_spec); + EXPECT_EQ(pool->getDdnsUpdateOnRenew(), bool_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsUpdateOnRenew(bool_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsConflictResolutionMode(string_spec); + EXPECT_EQ(pool->getDdnsConflictResolutionMode(), string_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsConflictResolutionMode(string_unspec); + + util::Optional double_unspec; + util::Optional double_spec(0.5); + + pool->setDdnsTtlPercent(double_spec); + EXPECT_EQ(pool->getDdnsTtlPercent(), double_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsTtlPercent(double_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + util::Optional int_unspec; + util::Optional int_spec(750); + + pool->setDdnsTtl(int_spec); + EXPECT_EQ(pool->getDdnsTtl(), int_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsTtl(int_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsTtlMin(int_spec); + EXPECT_EQ(pool->getDdnsTtlMin(), int_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsTtlMin(int_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setDdnsTtlMax(int_spec); + EXPECT_EQ(pool->getDdnsTtlMax(), int_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setDdnsTtlMax(int_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setHostnameCharSet(string_spec); + EXPECT_EQ(pool->getHostnameCharSet(), string_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setHostnameCharSet(string_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + + pool->setHostnameCharReplacement(string_spec); + EXPECT_EQ(pool->getHostnameCharReplacement(), string_spec); + EXPECT_TRUE(pool->hasDdnsParameters()); + pool->setHostnameCharReplacement(string_unspec); + EXPECT_FALSE(pool->hasDdnsParameters()); + } +}; + TEST(Pool4Test, constructorFirstLast) { // let's construct 192.0.2.1-192.0.2.255 pool @@ -127,6 +252,45 @@ TEST(Pool4Test, toElement) { " \"pool-id\": 5 " "}"; isc::test::runToElementTest(expected3, pool3); + + pool3.setDdnsSendUpdates(true); + pool3.setDdnsOverrideNoUpdate(true); + pool3.setDdnsOverrideClientUpdate(true); + pool3.setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER); + pool3.setDdnsGeneratedPrefix("prefix"); + pool3.setDdnsQualifyingSuffix("example.com."); + pool3.setDdnsUpdateOnRenew(false); + pool3.setDdnsConflictResolutionMode("check-without-dhcid"); + pool3.setDdnsTtlPercent(0.85); + pool3.setDdnsTtl(400); + pool3.setDdnsTtlMin(150); + pool3.setDdnsTtlMax(650); + pool3.setHostnameCharReplacement("x"); + pool3.setHostnameCharSet("[^A-Z]"); + + std::string expected4 = R"( + { + "pool": "192.0.2.0/25", + "option-data": [ ], + "pool-id": 5, + "ddns-send-updates": true, + "ddns-override-no-update": true, + "ddns-override-client-update": true, + "ddns-replace-client-name": "never", + "ddns-generated-prefix": "prefix", + "ddns-qualifying-suffix": "example.com.", + "ddns-update-on-renew": false, + "ddns-conflict-resolution-mode": "check-without-dhcid", + "ddns-ttl": 400, + "ddns-ttl-max": 650, + "ddns-ttl-min": 150, + "ddns-ttl-percent": 0.85, + "hostname-char-replacement": "x", + "hostname-char-set": "[^A-Z]" + })"; + + isc::test::runToElementTest(expected4, pool3); + } // This test checks that it is possible to specify pool specific options. @@ -669,4 +833,12 @@ TEST(Pool6Test, additionalClasses) { EXPECT_TRUE(pool.getAdditionalClasses().contains("foo")); } +TEST_F(PoolTest, ddnsParameters4) { + checkDdnsParamters(AF_INET); +} + +TEST_F(PoolTest, ddnsParameters6) { + checkDdnsParamters(AF_INET6); +} + } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc index 100660da86..a804be647a 100644 --- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc +++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc @@ -2283,4 +2283,231 @@ TEST_F(SrvConfigTest, sanityChecksDdnsTtlParameters) { } } +/// @brief Class for DdnsParams class. +class DdnsParamsTest : public testing::Test { +public: + /// @brief Constructor + DdnsParamsTest() = default; + + /// @brief Destructor + virtual ~DdnsParamsTest() = default; + + /// @brief Verifies that the DdnsParams accessors return parameter values + /// from the proper source. + /// + /// @param subnet Subnet under test. + /// @param address Address to locate the pool within the subnet via + /// DdnsParams::setPoolFromAddress(). + /// @param expected_pool expected Pool returned by setPoolFromAddress(). + void checkDdnsParameters(SubnetPtr subnet, IOAddress address, PoolPtr expected_pool) { + // Create DdnsParams instance with the subnet. + DdnsParamsPtr params(new DdnsParams(subnet, true)); + + // Attempt to locate the pool based on the given address. + PoolPtr pool = params->setPoolFromAddress(address); + + if (!expected_pool) { + // Pool should not have been found. + ASSERT_FALSE(pool); + } else { + // Pool should have been found. + ASSERT_TRUE(pool); + } + + // Verify each of the parameters comes from the expected source, either + // the subnet or the pool. + if (pool && !(pool->getDdnsSendUpdates().unspecified())) { + EXPECT_EQ(params->getEnableUpdates(), pool->getDdnsSendUpdates().get()); + } else { + EXPECT_EQ(params->getEnableUpdates(), subnet->getDdnsSendUpdates().get()); + } + + if (pool && !(pool->getDdnsOverrideNoUpdate().unspecified())) { + EXPECT_EQ(params->getOverrideNoUpdate(), pool->getDdnsOverrideNoUpdate().get()); + } else { + EXPECT_EQ(params->getOverrideNoUpdate(), subnet->getDdnsOverrideNoUpdate().get()); + } + + if (pool && !(pool->getDdnsOverrideClientUpdate().unspecified())) { + EXPECT_EQ(params->getOverrideClientUpdate(), pool->getDdnsOverrideClientUpdate().get()); + } else { + EXPECT_EQ(params->getOverrideClientUpdate(), subnet->getDdnsOverrideClientUpdate().get()); + } + + if (pool && !(pool->getDdnsReplaceClientNameMode().unspecified())) { + EXPECT_EQ(params->getReplaceClientNameMode(), pool->getDdnsReplaceClientNameMode().get()); + } else { + EXPECT_EQ(params->getReplaceClientNameMode(), subnet->getDdnsReplaceClientNameMode().get()); + } + + if (pool && !(pool->getDdnsGeneratedPrefix().unspecified())) { + EXPECT_EQ(params->getGeneratedPrefix(), pool->getDdnsGeneratedPrefix().get()); + } else { + EXPECT_EQ(params->getGeneratedPrefix(), subnet->getDdnsGeneratedPrefix().get()); + } + + if (pool && !(pool->getDdnsQualifyingSuffix().unspecified())) { + EXPECT_EQ(params->getQualifyingSuffix(), pool->getDdnsQualifyingSuffix().get()); + } else { + EXPECT_EQ(params->getQualifyingSuffix(), subnet->getDdnsQualifyingSuffix().get()); + } + + if (pool && !(pool->getDdnsUpdateOnRenew().unspecified())) { + EXPECT_EQ(params->getUpdateOnRenew(), pool->getDdnsUpdateOnRenew().get()); + } else { + EXPECT_EQ(params->getUpdateOnRenew(), subnet->getDdnsUpdateOnRenew().get()); + } + + if (pool && !(pool->getDdnsConflictResolutionMode().unspecified())) { + EXPECT_EQ(params->getConflictResolutionMode(), pool->getDdnsConflictResolutionMode().get()); + } else { + EXPECT_EQ(params->getConflictResolutionMode(), subnet->getDdnsConflictResolutionMode().get()); + } + + if (pool && !(pool->getDdnsTtlPercent().unspecified())) { + EXPECT_EQ(params->getTtlPercent(), pool->getDdnsTtlPercent().get()); + } else { + EXPECT_EQ(params->getTtlPercent(), subnet->getDdnsTtlPercent().get()); + } + + if (pool && !(pool->getDdnsTtl().unspecified())) { + EXPECT_EQ(params->getTtl(), pool->getDdnsTtl().get()); + } else { + EXPECT_EQ(params->getTtl(), subnet->getDdnsTtl().get()); + } + + if (pool && !(pool->getDdnsTtlMin().unspecified())) { + EXPECT_EQ(params->getTtlMin(), pool->getDdnsTtlMin().get()); + } else { + EXPECT_EQ(params->getTtlMin(), subnet->getDdnsTtlMin().get()); + } + + if (pool && !(pool->getDdnsTtlMax().unspecified())) { + EXPECT_EQ(params->getTtlMax(), pool->getDdnsTtlMax().get()); + } else { + EXPECT_EQ(params->getTtlMax(), subnet->getDdnsTtlMax().get()); + } + } +}; + +TEST_F(DdnsParamsTest, checkDdnsParameters4) { + // Create a subnet. + Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 10)); + + // Set values in the subnet for each of the DDNS parameters. + subnet->setDdnsSendUpdates(false); + subnet->setDdnsOverrideNoUpdate(true); + subnet->setDdnsOverrideClientUpdate(true); + subnet->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS); + subnet->setDdnsGeneratedPrefix("sn_prefix"); + subnet->setDdnsQualifyingSuffix("sn_suffix"); + subnet->setDdnsUpdateOnRenew(true); + subnet->setDdnsConflictResolutionMode("check-with-dhcid"); + subnet->setDdnsTtlPercent(0.75); + subnet->setDdnsTtl(500); + subnet->setDdnsTtlMin(250); + subnet->setDdnsTtlMax(750); + subnet->setHostnameCharReplacement("X"); + subnet->setHostnameCharSet("[^A-Z]"); + + // Create a pool and add it to the subnet. + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.100"))); + ASSERT_NO_THROW(subnet->addPool(pool)); + + { + // Values all come from subnet, address not in pool. + SCOPED_TRACE("Address not in pool"); + checkDdnsParameters(subnet, IOAddress("192.0.2.200"), Pool4Ptr()); + } + + { + // Values all come from subnet, address in pool but pool specifies no values. + SCOPED_TRACE("Pool exists but specifies none"); + checkDdnsParameters(subnet, IOAddress("192.0.2.50"), pool); + } + + // Set values in the pool for each of the DDNS parameters. + pool->setDdnsSendUpdates(true); + pool->setDdnsOverrideNoUpdate(false); + pool->setDdnsOverrideClientUpdate(false); + pool->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER); + pool->setDdnsGeneratedPrefix("pl_prefix"); + pool->setDdnsQualifyingSuffix("pl_suffix"); + pool->setDdnsUpdateOnRenew(false); + pool->setDdnsConflictResolutionMode("check-without-dhcid"); + pool->setDdnsTtlPercent(0.85); + pool->setDdnsTtl(400); + pool->setDdnsTtlMin(150); + pool->setDdnsTtlMax(650); + pool->setHostnameCharReplacement("y"); + pool->setHostnameCharSet("[^a-z]"); + + { + // Values all come from pool. + SCOPED_TRACE("Pool specifies all"); + checkDdnsParameters(subnet, IOAddress("192.0.2.50"), pool); + } +} + +TEST_F(DdnsParamsTest, checkDdnsParameters6) { + // Create a subnet. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, + 1, 2, 3, 4, SubnetID(1))); + + // Set values in the subnet for each of the DDNS parameters. + subnet->setDdnsSendUpdates(false); + subnet->setDdnsOverrideNoUpdate(true); + subnet->setDdnsOverrideClientUpdate(true); + subnet->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS); + subnet->setDdnsGeneratedPrefix("sn_prefix"); + subnet->setDdnsQualifyingSuffix("sn_suffix"); + subnet->setDdnsUpdateOnRenew(true); + subnet->setDdnsConflictResolutionMode("check-with-dhcid"); + subnet->setDdnsTtlPercent(0.75); + subnet->setDdnsTtl(500); + subnet->setDdnsTtlMin(250); + subnet->setDdnsTtlMax(750); + subnet->setHostnameCharReplacement("X"); + subnet->setHostnameCharSet("[^A-Z]"); + + // Create a pool and add it to the subnet. + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), + IOAddress("2001:db8:1::100"))); + ASSERT_NO_THROW(subnet->addPool(pool)); + + { + // Values all come from subnet, address not in pool. + SCOPED_TRACE("Address not in pool"); + checkDdnsParameters(subnet, IOAddress("2001:db8:1::200"), Pool6Ptr()); + } + + { + // Values all come from subnet, address in pool but pool specifies no values. + SCOPED_TRACE("Pool exists but specifies none"); + checkDdnsParameters(subnet, IOAddress("2001:db8:1::10"), pool); + } + + // Set values in the pool for each of the DDNS parameters. + pool->setDdnsSendUpdates(true); + pool->setDdnsOverrideNoUpdate(false); + pool->setDdnsOverrideClientUpdate(false); + pool->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER); + pool->setDdnsGeneratedPrefix("pl_prefix"); + pool->setDdnsQualifyingSuffix("pl_suffix"); + pool->setDdnsUpdateOnRenew(false); + pool->setDdnsConflictResolutionMode("check-without-dhcid"); + pool->setDdnsTtlPercent(0.85); + pool->setDdnsTtl(400); + pool->setDdnsTtlMin(150); + pool->setDdnsTtlMax(650); + pool->setHostnameCharReplacement("y"); + pool->setHostnameCharSet("[^a-z]"); + + { + // Values all come from pool. + SCOPED_TRACE("Pool specifies all"); + checkDdnsParameters(subnet, IOAddress("2001:db8:1::10"), pool); + } +} + } // end of anonymous namespace