diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcpsrv/lease_mgr.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcpsrv/lease_mgr.cc')
-rw-r--r-- | src/lib/dhcpsrv/lease_mgr.cc | 1270 |
1 files changed, 1270 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc new file mode 100644 index 0000000..2b588b6 --- /dev/null +++ b/src/lib/dhcpsrv/lease_mgr.cc @@ -0,0 +1,1270 @@ +// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/libdhcp++.h> +#include <dhcp/option_custom.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/dhcpsrv_log.h> +#include <dhcpsrv/lease_mgr.h> +#include <exceptions/exceptions.h> +#include <stats/stats_mgr.h> +#include <util/encode/hex.h> + +#include <boost/foreach.hpp> +#include <boost/algorithm/string.hpp> + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <limits> +#include <map> +#include <sstream> +#include <string> + +#include <time.h> + +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::db; +using namespace isc::dhcp; +using namespace isc::util; +using namespace std; + +namespace isc { +namespace dhcp { + +IOServicePtr LeaseMgr::io_service_ = IOServicePtr(); + +LeasePageSize::LeasePageSize(const size_t page_size) + : page_size_(page_size) { + + if (page_size_ == 0) { + isc_throw(OutOfRange, "page size of retrieved leases must not be 0"); + } + + if (page_size_ > std::numeric_limits<uint32_t>::max()) { + isc_throw(OutOfRange, "page size of retrieved leases must not be greater than " + << std::numeric_limits<uint32_t>::max()); + } +} + +Lease6Ptr +LeaseMgr::getLease6(Lease::Type type, const DUID& duid, + uint32_t iaid, SubnetID subnet_id) const { + Lease6Collection col = getLeases6(type, duid, iaid, subnet_id); + + if (col.size() > 1) { + isc_throw(MultipleRecords, "More than one lease found for type " + << static_cast<int>(type) << ", duid " + << duid.toText() << ", iaid " << iaid + << " and subnet-id " << subnet_id); + } + if (col.empty()) { + return (Lease6Ptr()); + } + return (*col.begin()); +} + +void +LeaseMgr::recountLeaseStats4() { + using namespace stats; + + StatsMgr& stats_mgr = StatsMgr::instance(); + + LeaseStatsQueryPtr query = startLeaseStatsQuery4(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Zero out the global stats. + // Cumulative counters ("reclaimed-declined-addresses", "reclaimed-leases", + // "cumulative-assigned-addresses") never get zeroed. + int64_t zero = 0; + stats_mgr.setValue("declined-addresses", zero); + + // Create if it does not exit reclaimed declined leases global stats. + if (!stats_mgr.getObservation("reclaimed-declined-addresses")) { + stats_mgr.setValue("reclaimed-declined-addresses", zero); + } + + // Create if it does not exit reclaimed leases global stats. + if (!stats_mgr.getObservation("reclaimed-leases")) { + stats_mgr.setValue("reclaimed-leases", zero); + } + + // Create if it does not exit cumulative global stats. + if (!stats_mgr.getObservation("cumulative-assigned-addresses")) { + stats_mgr.setValue("cumulative-assigned-addresses", zero); + } + + // Clear subnet level stats. This ensures we don't end up with corner + // cases that leave stale values in place. + const Subnet4Collection* subnets = + CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll(); + + for (Subnet4Collection::const_iterator subnet = subnets->begin(); + subnet != subnets->end(); ++subnet) { + SubnetID subnet_id = (*subnet)->getID(); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-addresses"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses"), + zero); + + const std::string name_rec_dec(StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses")); + if (!stats_mgr.getObservation(name_rec_dec)) { + stats_mgr.setValue(name_rec_dec, zero); + } + + const std::string name_rec(StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases")); + if (!stats_mgr.getObservation(name_rec)) { + stats_mgr.setValue(name_rec, zero); + } + + for (const auto& pool : (*subnet)->getPools(Lease::TYPE_V4)) { + const std::string name_aa(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "assigned-addresses"))); + if (!stats_mgr.getObservation(name_aa)) { + stats_mgr.setValue(name_aa, zero); + } + + const std::string& name_da(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "declined-addresses"))); + if (!stats_mgr.getObservation(name_da)) { + stats_mgr.setValue(name_da, zero); + } + + const std::string& name_rec_dec(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "reclaimed-declined-addresses"))); + if (!stats_mgr.getObservation(name_rec_dec)) { + stats_mgr.setValue(name_rec_dec, zero); + } + + const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "reclaimed-leases"))); + if (!stats_mgr.getObservation(name_rec)) { + stats_mgr.setValue(name_rec, zero); + } + } + } + + // Get counts per state per subnet. Iterate over the result set + // updating the subnet and global values. + LeaseStatsRow row; + while (query->getNextRow(row)) { + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Add to subnet level value. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-addresses"), + row.state_count_); + } else if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + "declined-addresses"), + row.state_count_); + + // Add to the global value. + stats_mgr.addValue("declined-addresses", row.state_count_); + + // Add to subnet level value. + // Declined leases also count as assigned. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-addresses"), + row.state_count_); + } + } + + query = startPoolLeaseStatsQuery4(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Get counts per state per subnet and pool. Iterate over the result set + // updating the subnet and pool and global values. + while (query->getNextRow(row)) { + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Add to subnet and pool level value. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "assigned-addresses")), + row.state_count_); + } else if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet and pool level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "declined-addresses")), + row.state_count_); + + // Add to subnet and pool level value. + // Declined leases also count as assigned. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "assigned-addresses")), + row.state_count_); + } + } +} + +LeaseStatsQuery::LeaseStatsQuery(const SelectMode& select_mode) + : first_subnet_id_(0), last_subnet_id_(0), select_mode_(select_mode) { + if (select_mode != ALL_SUBNETS && select_mode != ALL_SUBNET_POOLS) { + isc_throw(BadValue, "LeaseStatsQuery: mode must be either ALL_SUBNETS or ALL_SUBNET_POOLS"); + } +} + +LeaseStatsQuery::LeaseStatsQuery(const SubnetID& subnet_id) + : first_subnet_id_(subnet_id), last_subnet_id_(0), + select_mode_(SINGLE_SUBNET) { + + if (first_subnet_id_ == 0) { + isc_throw(BadValue, "LeaseStatsQuery: subnet_id_ must be > 0"); + } +} + +LeaseStatsQuery::LeaseStatsQuery(const SubnetID& first_subnet_id, + const SubnetID& last_subnet_id) + : first_subnet_id_(first_subnet_id), last_subnet_id_(last_subnet_id), + select_mode_(SUBNET_RANGE) { + + if (first_subnet_id_ == 0) { + isc_throw(BadValue, "LeaseStatsQuery: first_subnet_id_ must be > 0"); + } + + if (last_subnet_id_ == 0) { + isc_throw(BadValue, "LeaseStatsQuery: last_subnet_id_ must be > 0"); + } + + if (last_subnet_id_ <= first_subnet_id_) { + isc_throw(BadValue, + "LeaseStatsQuery: last_subnet_id_must be > first_subnet_id_"); + } +} + +LeaseStatsQueryPtr +LeaseMgr::startLeaseStatsQuery4() { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startPoolLeaseStatsQuery4() { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& /* subnet_id */) { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& /* first_subnet_id */, + const SubnetID& /* last_subnet_id */) { + return(LeaseStatsQueryPtr()); +} + +bool +LeaseStatsQuery::getNextRow(LeaseStatsRow& /*row*/) { + return (false); +} + +void +LeaseMgr::recountLeaseStats6() { + using namespace stats; + + StatsMgr& stats_mgr = StatsMgr::instance(); + + LeaseStatsQueryPtr query = startLeaseStatsQuery6(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Zero out the global stats. + // Cumulative counters ("reclaimed-declined-addresses", "reclaimed-leases", + // "cumulative-assigned-nas", "cumulative-assigned-pds") never get zeroed. + int64_t zero = 0; + stats_mgr.setValue("declined-addresses", zero); + + if (!stats_mgr.getObservation("reclaimed-declined-addresses")) { + stats_mgr.setValue("reclaimed-declined-addresses", zero); + } + + if (!stats_mgr.getObservation("reclaimed-leases")) { + stats_mgr.setValue("reclaimed-leases", zero); + } + + // Create if it does not exit cumulative nas global stats. + if (!stats_mgr.getObservation("cumulative-assigned-nas")) { + stats_mgr.setValue("cumulative-assigned-nas", zero); + } + + // Create if it does not exit cumulative pds global stats. + if (!stats_mgr.getObservation("cumulative-assigned-pds")) { + stats_mgr.setValue("cumulative-assigned-pds", zero); + } + + // Clear subnet level stats. This ensures we don't end up with corner + // cases that leave stale values in place. + const Subnet6Collection* subnets = + CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll(); + + for (Subnet6Collection::const_iterator subnet = subnets->begin(); + subnet != subnets->end(); ++subnet) { + SubnetID subnet_id = (*subnet)->getID(); + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-nas"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "assigned-pds"), + zero); + + stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id, + "declined-addresses"), + zero); + + if (!stats_mgr.getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses"))) { + stats_mgr.setValue( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-declined-addresses"), + zero); + } + + if (!stats_mgr.getObservation( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases"))) { + stats_mgr.setValue( + StatsMgr::generateName("subnet", subnet_id, + "reclaimed-leases"), + zero); + } + + for (const auto& pool : (*subnet)->getPools(Lease::TYPE_NA)) { + const std::string& name_anas(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "assigned-nas"))); + if (!stats_mgr.getObservation(name_anas)) { + stats_mgr.setValue(name_anas, zero); + } + + const std::string& name_da(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "declined-addresses"))); + if (!stats_mgr.getObservation(name_da)) { + stats_mgr.setValue(name_da, zero); + } + + const std::string name_rec_dec(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "reclaimed-declined-addresses"))); + if (!stats_mgr.getObservation(name_rec_dec)) { + stats_mgr.setValue(name_rec_dec, zero); + } + + const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pool", pool->getID(), + "reclaimed-leases"))); + if (!stats_mgr.getObservation(name_rec)) { + stats_mgr.setValue(name_rec, zero); + } + } + + for (const auto& pool : (*subnet)->getPools(Lease::TYPE_PD)) { + const std::string& name_apds(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", pool->getID(), + "assigned-pds"))); + if (!stats_mgr.getObservation(name_apds)) { + stats_mgr.setValue(name_apds, zero); + } + + const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id, + StatsMgr::generateName("pd-pool", pool->getID(), + "reclaimed-leases"))); + if (!stats_mgr.getObservation(name_rec)) { + stats_mgr.setValue(name_rec, zero); + } + } + } + + // Get counts per state per subnet. Iterate over the result set + // updating the subnet and global values. + LeaseStatsRow row; + while (query->getNextRow(row)) { + switch(row.lease_type_) { + case Lease::TYPE_NA: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Add to subnet level value. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-nas"), + row.state_count_); + } else if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + "declined-addresses"), + row.state_count_); + + // Add to the global value. + stats_mgr.addValue("declined-addresses", row.state_count_); + + // Add to subnet level value. + // Declined leases also count as assigned. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-nas"), + row.state_count_); + } + break; + + case Lease::TYPE_PD: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Set subnet level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + "assigned-pds"), + row.state_count_); + } + break; + + default: + // We don't support TYPE_TAs yet + break; + } + } + + query = startPoolLeaseStatsQuery6(); + if (!query) { + /// NULL means not backend does not support recounting. + return; + } + + // Get counts per state per subnet and pool. Iterate over the result set + // updating the subnet and pool and global values. + while (query->getNextRow(row)) { + switch(row.lease_type_) { + case Lease::TYPE_NA: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Add to subnet and pool level value. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "assigned-nas")), + row.state_count_); + } else if (row.lease_state_ == Lease::STATE_DECLINED) { + // Set subnet and pool level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "declined-addresses")), + row.state_count_); + + // Add to subnet and pool level value. + // Declined leases also count as assigned. + stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pool", row.pool_id_, + "assigned-nas")), + row.state_count_); + } + break; + + case Lease::TYPE_PD: + if (row.lease_state_ == Lease::STATE_DEFAULT) { + // Set subnet and pool level value. + stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_, + StatsMgr::generateName("pd-pool", row.pool_id_, + "assigned-pds")), + row.state_count_); + } + break; + + default: + // We don't support TYPE_TAs yet + break; + } + } +} + +LeaseStatsQueryPtr +LeaseMgr::startLeaseStatsQuery6() { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startPoolLeaseStatsQuery6() { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& /* subnet_id */) { + return(LeaseStatsQueryPtr()); +} + +LeaseStatsQueryPtr +LeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& /* first_subnet_id */, + const SubnetID& /* last_subnet_id */) { + return(LeaseStatsQueryPtr()); +} + +std::string +LeaseMgr::getDBVersion() { + isc_throw(NotImplemented, "LeaseMgr::getDBVersion() called"); +} + +void +LeaseMgr::setExtendedInfoTablesEnabled(const DatabaseConnection::ParameterMap& parameters) { + std::string extended_info_tables; + try { + extended_info_tables = parameters.at("extended-info-tables"); + } catch (const exception&) { + extended_info_tables = "false"; + } + // If extended_info_tables is 'true' we will enable them. + if (extended_info_tables == "true") { + setExtendedInfoTablesEnabled(true); + } +} + +bool +LeaseMgr::upgradeLease4ExtendedInfo(const Lease4Ptr& lease, + CfgConsistency::ExtendedInfoSanity check) { + static OptionDefinitionPtr rai_def; + + bool changed = false; + if (!lease) { + return (changed); + } + + if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) { + return (changed); + } + + ConstElementPtr user_context = lease->getContext(); + if (!user_context) { + return (changed); + } + + if (!rai_def) { + rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_AGENT_OPTIONS); + } + + if (!rai_def) { + // The definition is set when libdhcp++ is loaded so it is impossible + // to not be able to get it... so should not happen! + isc_throw(Unexpected, "can't find RAI option definition?!"); + } + + ConstElementPtr isc; + ConstElementPtr extended_info; + ElementPtr mutable_user_context; + ElementPtr mutable_isc; + string verifying = ""; + bool removed_extended_info = false; + + try { + verifying = "user context"; + if (user_context->getType() != Element::map) { + isc_throw(BadValue, "user context is not a map"); + } + if (user_context->empty()) { + changed = true; + lease->setContext(ConstElementPtr()); + return (changed); + } + + verifying = "isc"; + isc = user_context->get("ISC"); + if (!isc) { + return (changed); + } + mutable_user_context = + boost::const_pointer_cast<Element>(user_context); + if (!mutable_user_context) { + // Should not happen... + mutable_user_context = copy(user_context, 0); + lease->setContext(mutable_user_context); + } + + if (isc->getType() != Element::map) { + isc_throw(BadValue, "ISC entry is not a map"); + } + if (isc->empty()) { + changed = true; + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + return (changed); + } + + verifying = "relay-agent-info"; + extended_info = isc->get("relay-agent-info"); + if (!extended_info) { + return (changed); + } + mutable_isc = boost::const_pointer_cast<Element>(isc); + if (!mutable_isc) { + // Should not happen... + mutable_isc = copy(isc, 0); + mutable_user_context->set("ISC", mutable_isc); + } + + if (extended_info->getType() == Element::string) { + // Upgrade + changed = true; + ElementPtr upgraded = Element::createMap(); + upgraded->set("sub-options", extended_info); + mutable_isc->set("relay-agent-info", upgraded); + + // Try to decode sub-options. + verifying = "rai"; + string rai_hex = extended_info->stringValue(); + vector<uint8_t> rai_data; + str::decodeFormattedHexString(rai_hex, rai_data); + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4, rai_data)); + if (!rai) { + isc_throw(BadValue, "can't create RAI option"); + } + + OptionPtr remote_id = rai->getOption(RAI_OPTION_REMOTE_ID); + if (remote_id) { + vector<uint8_t> bytes = remote_id->toBinary(false); + if (bytes.size() > 0) { + upgraded->set("remote-id", + Element::create(encode::encodeHex(bytes))); + } + } + + OptionPtr relay_id = rai->getOption(RAI_OPTION_RELAY_ID); + if (relay_id) { + vector<uint8_t> bytes = relay_id->toBinary(false); + if (bytes.size() > 0) { + upgraded->set("relay-id", + Element::create(encode::encodeHex(bytes))); + } + } + + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED) + .arg(lease->addr_.toText()); + return (changed); + } else if (extended_info->getType() != Element::map) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "relay-agent-info is not a map or a string"); + } + + if (check == CfgConsistency::EXTENDED_INFO_CHECK_FIX) { + return (changed); + } + + // Try to decode sub-options. + ConstElementPtr sub_options = extended_info->get("sub-options"); + if (sub_options) { + verifying = "sub-options"; + if (sub_options->getType() != Element::string) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "sub-options is not a string"); + } + string rai_hex = sub_options->stringValue(); + vector<uint8_t> rai_data; + str::decodeFormattedHexString(rai_hex, rai_data); + } + + ConstElementPtr remote_id = extended_info->get("remote-id"); + if (remote_id) { + verifying = "remote-id"; + if (remote_id->getType() != Element::string) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "remote-id is not a string"); + } + string remote_id_hex = remote_id->stringValue(); + vector<uint8_t> remote_id_data; + encode::decodeHex(remote_id_hex, remote_id_data); + if (remote_id_data.empty()) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "remote-id is empty"); + } + } + + ConstElementPtr relay_id = extended_info->get("relay-id"); + if (relay_id) { + verifying = "relay-id"; + if (relay_id->getType() != Element::string) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "relay-id is not a string"); + } + string relay_id_hex = relay_id->stringValue(); + vector<uint8_t> relay_id_data; + encode::decodeHex(relay_id_hex, relay_id_data); + if (relay_id_data.empty()) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "relay-id is empty"); + } + } + + if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) { + return (changed); + } + + verifying = "relay-agent-info"; + for (auto elem : extended_info->mapValue()) { + if ((elem.first != "sub-options") && + (elem.first != "remote-id") && + (elem.first != "relay-id") && + (elem.first != "comment")) { + mutable_isc->remove("relay-agent-info"); + removed_extended_info = true; + isc_throw(BadValue, "spurious '" << elem.first << + "' entry in relay-agent-info"); + } + } + + return (changed); + } catch (const exception& ex) { + ostringstream err; + err << "in " << verifying << " a problem was found: " << ex.what(); + LOG_ERROR(dhcpsrv_logger, DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL) + .arg(lease->addr_.toText()) + .arg(err.str()); + + changed = true; + if (verifying == "user context") { + lease->setContext(ConstElementPtr()); + } else if (verifying == "isc") { + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + } else { + if (!removed_extended_info) { + mutable_isc->remove("relay-agent-info"); + } + if (mutable_isc->empty()) { + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + } + } + return (changed); + } +} + +bool +LeaseMgr::upgradeLease6ExtendedInfo(const Lease6Ptr& lease, + CfgConsistency::ExtendedInfoSanity check) { + bool changed = false; + if (!lease) { + return (changed); + } + + if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) { + return (changed); + } + + ConstElementPtr user_context = lease->getContext(); + if (!user_context) { + return (changed); + } + + ConstElementPtr isc; + ConstElementPtr relay_info; + ElementPtr mutable_user_context; + ElementPtr mutable_isc; + string verifying = ""; + bool removed_relay_info = false; + bool upgraded = false; + bool have_both = false; + int i = -1; + + try { + verifying = "user context"; + if (user_context->getType() != Element::map) { + isc_throw(BadValue, "user context is not a map"); + } + if (user_context->empty()) { + changed = true; + lease->setContext(ConstElementPtr()); + return (changed); + } + + verifying = "isc"; + isc = user_context->get("ISC"); + if (!isc) { + return (changed); + } + mutable_user_context = + boost::const_pointer_cast<Element>(user_context); + if (!mutable_user_context) { + // Should not happen... + mutable_user_context = copy(user_context, 0); + lease->setContext(mutable_user_context); + } + + if (isc->getType() != Element::map) { + isc_throw(BadValue, "ISC entry is not a map"); + } + if (isc->empty()) { + changed = true; + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + return (changed); + } + mutable_isc = boost::const_pointer_cast<Element>(isc); + if (!mutable_isc) { + // Should not happen... + mutable_isc = copy(isc, 0); + mutable_user_context->set("ISC", mutable_isc); + } + + relay_info = mutable_isc->get("relays"); + if (relay_info && isc->contains("relay-info")) { + changed = true; + mutable_isc->remove("relays"); + have_both = true; + relay_info.reset(); + } + if (relay_info) { + // Upgrade + changed = true; + upgraded = true; + verifying = "relays"; + mutable_isc->set("relay-info", relay_info); + mutable_isc->remove("relays"); + + if (relay_info->getType() != Element::list) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relays is not a list"); + } + if (relay_info->empty()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relays is empty"); + } + + verifying = "relay"; + for (i = 0; i < relay_info->size(); ++i) { + ElementPtr relay = relay_info->getNonConst(i); + if (!relay) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "null relay#" << i); + } + if (relay->getType() != Element::map) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay#" << i << " is not a map"); + } + + // Try to decode options. + ConstElementPtr options = relay->get("options"); + if (!options) { + continue; + } + + verifying = "options"; + if (options->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "options is not a string"); + } + string options_hex = options->stringValue(); + vector<uint8_t> options_data; + str::decodeFormattedHexString(options_hex, options_data); + OptionCollection opts; + LibDHCP::unpackOptions6(options_data, DHCP6_OPTION_SPACE, opts); + + auto remote_id_it = opts.find(D6O_REMOTE_ID); + if (remote_id_it != opts.end()) { + OptionPtr remote_id = remote_id_it->second; + if (remote_id) { + vector<uint8_t> bytes = remote_id->toBinary(false); + if (bytes.size() > 0) { + relay->set("remote-id", + Element::create(encode::encodeHex(bytes))); + } + } + } + + auto relay_id_it = opts.find(D6O_RELAY_ID); + if (relay_id_it != opts.end()) { + OptionPtr relay_id = relay_id_it->second; + if (relay_id) { + vector<uint8_t> bytes = relay_id->toBinary(false); + if (bytes.size() > 0) { + relay->set("relay-id", + Element::create(encode::encodeHex(bytes))); + } + } + } + } + } + + verifying = (upgraded ? "relays" : "relay-info"); + i = -1; + relay_info = mutable_isc->get("relay-info"); + if (!relay_info) { + return (changed); + } + if (!upgraded && (relay_info->getType() != Element::list)) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay-info is not a list"); + } + if (!upgraded && relay_info->empty()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay-info is empty"); + } + + verifying = "relay"; + for (i = 0; i < relay_info->size(); ++i) { + ElementPtr relay = relay_info->getNonConst(i); + if (!upgraded && !relay) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "null relay#" << i); + } + if (!upgraded && (relay->getType() != Element::map)) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay#" << i << " is not a map"); + } + + ConstElementPtr options = relay->get("options"); + if (!upgraded && options) { + // Try to decode options. + verifying = "options"; + if (options->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "options is not a string"); + } + string options_hex = options->stringValue(); + vector<uint8_t> options_data; + str::decodeFormattedHexString(options_hex, options_data); + OptionCollection opts; + LibDHCP::unpackOptions6(options_data, DHCP6_OPTION_SPACE, opts); + } + if (check == CfgConsistency::EXTENDED_INFO_CHECK_FIX) { + continue; + } + + verifying = "link"; + ConstElementPtr link_addr = relay->get("link"); + if (!link_addr) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "no link"); + } + if (link_addr->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "link is not a string"); + } + IOAddress laddr(link_addr->stringValue()); + if (!laddr.isV6()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "link is not an IPv6 address"); + } + + ConstElementPtr remote_id = relay->get("remote-id"); + if (!upgraded && remote_id) { + verifying = "remote-id"; + if (remote_id->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "remote-id is not a string"); + } + string remote_id_hex = remote_id->stringValue(); + vector<uint8_t> remote_id_data; + encode::decodeHex(remote_id_hex, remote_id_data); + if (remote_id_data.empty()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "remote-id is empty"); + } + } + + ConstElementPtr relay_id = relay->get("relay-id"); + if (!upgraded && relay_id) { + verifying = "relay-id"; + if (relay_id->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay-id is not a string"); + } + string relay_id_hex = relay_id->stringValue(); + vector<uint8_t> relay_id_data; + encode::decodeHex(relay_id_hex, relay_id_data); + if (relay_id_data.empty()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "relay-id is empty"); + } + } + + if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) { + continue; + } + + verifying = "peer"; + ConstElementPtr peer_addr = relay->get("peer"); + if (!peer_addr) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "no peer"); + } + if (peer_addr->getType() != Element::string) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "peer is not a string"); + } + IOAddress paddr(peer_addr->stringValue()); + if (!paddr.isV6()) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "peer is not an IPv6 address"); + } + + verifying = "hop"; + ConstElementPtr hop = relay->get("hop"); + if (!hop) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "no hop"); + } + if (hop->getType() != Element::integer) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "hop is not an integer"); + } + + verifying = (upgraded ? "relays" : "relay-info"); + for (auto elem : relay->mapValue()) { + if ((elem.first != "hop") && + (elem.first != "link") && + (elem.first != "peer") && + (elem.first != "options") && + (elem.first != "remote-id") && + (elem.first != "relay-id") && + (elem.first != "comment")) { + mutable_isc->remove("relay-info"); + removed_relay_info = true; + isc_throw(BadValue, "spurious '" << elem.first << "' entry"); + } + } + } + + if (upgraded) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED) + .arg(lease->addr_.toText()); + } + + return (changed); + } catch (const exception& ex) { + ostringstream err; + err << "in " << verifying; + if (i >= 0) { + err << " [relay#" << i << "]"; + } + err << " a problem was found: " << ex.what(); + LOG_ERROR(dhcpsrv_logger, DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL) + .arg(lease->addr_.toText()) + .arg(err.str()); + + changed = true; + have_both = !have_both; + if (verifying == "user context") { + lease->setContext(ConstElementPtr()); + } else if (verifying == "isc") { + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + } else { + if (!removed_relay_info) { + mutable_isc->remove("relay-info"); + } + if (mutable_isc->empty()) { + mutable_user_context->remove("ISC"); + if (mutable_user_context->empty()) { + lease->setContext(ConstElementPtr()); + } + } + } + return (changed); + } +} + +void +LeaseMgr::extractLease4ExtendedInfo(const Lease4Ptr& lease, + bool ignore_errors) { + if (!lease) { + return; + } + + ConstElementPtr user_context = lease->getContext(); + if (!user_context) { + return; + } + if (user_context->getType() != Element::map) { + if (ignore_errors) { + return; + } + isc_throw(BadValue, "user context is not a map"); + } + if (user_context->empty()) { + return; + } + + ConstElementPtr isc = user_context->get("ISC"); + if (!isc) { + return; + } + if (isc->getType() != Element::map) { + if (ignore_errors) { + return; + } + isc_throw(BadValue, "ISC entry is not a map"); + } + if (isc->empty()) { + return; + } + + ConstElementPtr extended_info = isc->get("relay-agent-info"); + if (!extended_info) { + return; + } + if (extended_info->getType() != Element::map) { + if (ignore_errors) { + return; + } + isc_throw(BadValue, "relay-agent-info is not a map"); + } + if (extended_info->empty()) { + return; + } + + ConstElementPtr relay_id = extended_info->get("relay-id"); + if (relay_id) { + if (relay_id->getType() == Element::string) { + vector<uint8_t> bytes; + try { + encode::decodeHex(relay_id->stringValue(), bytes); + } catch (...) { + // Decode failed + if (!ignore_errors) { + throw; + } + } + lease->relay_id_ = bytes; + } else if (!ignore_errors) { + isc_throw(BadValue, "relay-id entry is not a string"); + } + } + + ConstElementPtr remote_id = extended_info->get("remote-id"); + if (remote_id) { + if (remote_id->getType() == Element::string) { + vector<uint8_t> bytes; + try { + encode::decodeHex(remote_id->stringValue(), bytes); + } catch (...) { + // Decode failed + if (!ignore_errors) { + throw; + } + } + lease->remote_id_ = bytes; + } else if (!ignore_errors) { + isc_throw(BadValue, "remote-id entry is not a string"); + } + } +} + +bool +LeaseMgr::addExtendedInfo6(const Lease6Ptr& lease) { + + bool added = false; + if (!lease) { + return (added); + } + + ConstElementPtr user_context = lease->getContext(); + if (!user_context || (user_context->getType() != Element::map) || + user_context->empty()) { + return (added); + } + + ConstElementPtr isc = user_context->get("ISC"); + if (!isc || (isc->getType() != Element::map) || isc->empty()) { + return (added); + } + + ConstElementPtr relay_info = isc->get("relay-info"); + if (!relay_info || (relay_info->getType() != Element::list) || + relay_info->empty()) { + return (added); + } + + for (int i = 0; i < relay_info->size(); ++i) { + ConstElementPtr relay = relay_info->get(i); + if (!relay || (relay->getType() != Element::map) || relay->empty()) { + continue; + } + try { + ConstElementPtr relay_id = relay->get("relay-id"); + if (relay_id) { + string relay_id_hex = relay_id->stringValue(); + vector<uint8_t> relay_id_data; + encode::decodeHex(relay_id_hex, relay_id_data); + if (relay_id_data.empty()) { + continue; + } + addRelayId6(lease->addr_, relay_id_data); + added = true; + } + + ConstElementPtr remote_id = relay->get("remote-id"); + if (remote_id) { + string remote_id_hex = remote_id->stringValue(); + vector<uint8_t> remote_id_data; + encode::decodeHex(remote_id_hex, remote_id_data); + if (remote_id_data.empty()) { + continue; + } + addRemoteId6(lease->addr_, remote_id_data); + added = true; + } + } catch (const exception&) { + continue; + } + } + return (added); +} + +} // namespace isc::dhcp +} // namespace isc |