// 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 #include #include #include #include #include #include #include #include #include using namespace isc::asiolink; using namespace isc::util; using namespace isc::data; using namespace std; namespace isc { namespace dhcp { const uint32_t Lease::STATE_DEFAULT = 0x0; const uint32_t Lease::STATE_DECLINED = 0x1; const uint32_t Lease::STATE_EXPIRED_RECLAIMED = 0x2; std::string Lease::lifetimeToText(uint32_t lifetime) { ostringstream repr; if (lifetime == INFINITY_LFT) { repr << "infinity"; } else { repr << lifetime; } return repr.str(); } Lease::Lease(const isc::asiolink::IOAddress& addr, 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), valid_lft_(valid_lft), current_valid_lft_(valid_lft), reuseable_valid_lft_(0), cltt_(cltt), current_cltt_(cltt), subnet_id_(subnet_id), pool_id_(0), hostname_(boost::algorithm::to_lower_copy(hostname)), fqdn_fwd_(fqdn_fwd), fqdn_rev_(fqdn_rev), hwaddr_(hwaddr), state_(STATE_DEFAULT) { } std::string Lease::typeToText(Lease::Type type) { switch (type) { case Lease::TYPE_V4: return string("V4"); case Lease::TYPE_NA: return string("IA_NA"); case Lease::TYPE_TA: return string("IA_TA"); case Lease::TYPE_PD: return string("IA_PD"); break; default: { stringstream tmp; tmp << "unknown (" << type << ")"; return (tmp.str()); } } } Lease::Type Lease::textToType(const std::string& text) { if (text == "V4") { return (TYPE_V4); } else if (text == "IA_NA") { return (TYPE_NA); } else if (text == "IA_TA") { return (TYPE_TA); } else if (text == "IA_PD") { return (TYPE_PD); } isc_throw(BadValue, "unsupported lease type " << text); } std::string Lease::basicStatesToText(const uint32_t state) { switch (state) { case STATE_DEFAULT: return ("default"); case STATE_DECLINED: return ("declined"); case STATE_EXPIRED_RECLAIMED: return ("expired-reclaimed"); default: // The default case will be handled further on ; } std::ostringstream s; s << "unknown (" << state << ")"; return s.str(); } bool Lease::expired() const { return ((valid_lft_ != INFINITY_LFT) && (getExpirationTime() < time(NULL))); } bool Lease::stateExpiredReclaimed() const { return (state_ == STATE_EXPIRED_RECLAIMED); } bool Lease::stateDeclined() const { return (state_ == STATE_DECLINED); } int64_t Lease::getExpirationTime() const { return (static_cast(cltt_) + valid_lft_); } bool Lease::hasIdenticalFqdn(const Lease& other) const { return (boost::algorithm::iequals(hostname_, other.hostname_) && fqdn_fwd_ == other.fqdn_fwd_ && fqdn_rev_ == other.fqdn_rev_); } void Lease::fromElementCommon(const LeasePtr& lease, const data::ConstElementPtr& element) { if (!element) { isc_throw(BadValue, "parsed lease data is null"); } if (element->getType() != Element::map) { isc_throw(BadValue, "parsed lease data is not a JSON map"); } if (!lease) { isc_throw(Unexpected, "pointer to parsed lease is null"); } // IP address. ConstElementPtr ip_address = element->get("ip-address"); if (!ip_address || (ip_address->getType() != Element::string)) { isc_throw(BadValue, "ip-address not present in the parsed lease" " or it is not a string"); } boost::scoped_ptr io_address; try { io_address.reset(new asiolink::IOAddress(ip_address->stringValue())); } catch (const std::exception& ex) { isc_throw(BadValue, "invalid IP address " << ip_address->stringValue() << " in the parsed lease"); } lease->addr_ = *io_address; // Subnet identifier. ConstElementPtr subnet_id = element->get("subnet-id"); if (!subnet_id || (subnet_id->getType() != Element::integer)) { isc_throw(BadValue, "subnet-id not present in the parsed lease" " or it is not an integer"); } if (subnet_id->intValue() <= 0) { isc_throw(BadValue, "subnet-id " << subnet_id->intValue() << " is not" << " a positive integer"); } else if (subnet_id->intValue() > numeric_limits::max()) { isc_throw(BadValue, "subnet-id " << subnet_id->intValue() << " is not" << " a 32 bit unsigned integer"); } lease->subnet_id_ = SubnetID(subnet_id->intValue()); // Pool identifier. ConstElementPtr pool_id = element->get("pool-id"); if (pool_id) { if (pool_id->getType() != Element::integer) { isc_throw(BadValue, "pool-id is not an integer"); } if (pool_id->intValue() <= 0) { isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not" << " a positive integer"); } else if (pool_id->intValue() > numeric_limits::max()) { isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not" << " a 32 bit unsigned integer"); } lease->pool_id_ = pool_id->intValue(); } // Hardware address. ConstElementPtr hw_address = element->get("hw-address"); if (hw_address) { if (hw_address->getType() != Element::string) { isc_throw(BadValue, "hw-address is not a string in the parsed lease"); } try { HWAddr parsed_hw_address = HWAddr::fromText(hw_address->stringValue()); lease->hwaddr_.reset(new HWAddr(parsed_hw_address.hwaddr_, HTYPE_ETHER)); } catch (const std::exception& ex) { isc_throw(BadValue, "invalid hardware address " << hw_address->stringValue() << " in the parsed lease"); } } // cltt ConstElementPtr cltt = element->get("cltt"); if (!cltt || (cltt->getType() != Element::integer)) { isc_throw(BadValue, "cltt is not present in the parsed lease" " or it is not an integer"); } if (cltt->intValue() <= 0) { isc_throw(BadValue, "cltt " << cltt->intValue() << " is not a" " positive integer in the parsed lease"); } lease->cltt_ = static_cast(cltt->intValue()); // valid lifetime ConstElementPtr valid_lifetime = element->get("valid-lft"); if (!valid_lifetime || (valid_lifetime->getType() != Element::integer)) { isc_throw(BadValue, "valid-lft is not present in the parsed lease" " or it is not an integer"); } if (valid_lifetime->intValue() < 0) { isc_throw(BadValue, "valid-lft " << valid_lifetime->intValue() << " is negative in the parsed lease"); } lease->valid_lft_ = valid_lifetime->intValue(); // fqdn-fwd ConstElementPtr fqdn_fwd = element->get("fqdn-fwd"); if (!fqdn_fwd || fqdn_fwd->getType() != Element::boolean) { isc_throw(BadValue, "fqdn-fwd is not present in the parsed lease" " or it is not a boolean value"); } lease->fqdn_fwd_ = fqdn_fwd->boolValue(); // fqdn-fwd ConstElementPtr fqdn_rev = element->get("fqdn-rev"); if (!fqdn_rev || (fqdn_rev->getType() != Element::boolean)) { isc_throw(BadValue, "fqdn-rev is not present in the parsed lease" " or it is not a boolean value"); } lease->fqdn_rev_ = fqdn_rev->boolValue(); // hostname ConstElementPtr hostname = element->get("hostname"); if (!hostname || (hostname->getType() != Element::string)) { isc_throw(BadValue, "hostname is not present in the parsed lease" " or it is not a string value"); } lease->hostname_ = hostname->stringValue(); boost::algorithm::to_lower(lease->hostname_); // state ConstElementPtr state = element->get("state"); if (!state || (state->getType() != Element::integer)) { isc_throw(BadValue, "state is not present in the parsed lease" " or it is not an integer"); } if ((state->intValue() < 0) || (state->intValue() > Lease::STATE_EXPIRED_RECLAIMED)) { isc_throw(BadValue, "state " << state->intValue() << " must be in range [0.." << Lease::STATE_EXPIRED_RECLAIMED << "]"); } lease->state_ = state->intValue(); // user context ConstElementPtr ctx = element->get("user-context"); if (ctx) { if (ctx->getType() != Element::map) { isc_throw(BadValue, "user context is not a map"); } lease->setContext(ctx); } lease->updateCurrentExpirationTime(); } void Lease::updateCurrentExpirationTime() { Lease::syncCurrentExpirationTime(*this, *this); } void Lease::syncCurrentExpirationTime(const Lease& from, Lease& to) { to.current_cltt_ = from.cltt_; to.current_valid_lft_ = from.valid_lft_; } Lease4::Lease4(const isc::asiolink::IOAddress& address, const HWAddrPtr& hw_address, const ClientIdPtr& client_id, const uint32_t valid_lifetime, const time_t cltt, const SubnetID subnet_id, const bool fqdn_fwd, const bool fqdn_rev, const std::string& hostname) : Lease(address, valid_lifetime, subnet_id, cltt, fqdn_fwd, fqdn_rev, hostname, hw_address), client_id_(client_id), remote_id_(), relay_id_() { } Lease4::Lease4() : Lease(0, 0, 0, 0, false, false, "", HWAddrPtr()) { } std::string Lease4::statesToText(const uint32_t state) { return (Lease::basicStatesToText(state)); } const std::vector& Lease4::getClientIdVector() const { if (!client_id_) { static std::vector empty_vec; return (empty_vec); } return (client_id_->getClientId()); } const std::vector& Lease::getHWAddrVector() const { if (!hwaddr_) { static std::vector empty_vec; return (empty_vec); } return (hwaddr_->hwaddr_); } bool Lease4::belongsToClient(const HWAddrPtr& hw_address, const ClientIdPtr& client_id) const { // If client id matches, lease matches. if (equalValues(client_id, client_id_)) { return (true); } else if (!client_id || !client_id_) { // If client id is unspecified, use HW address. if (equalValues(hw_address, hwaddr_)) { return (true); } } return (false); } void Lease4::decline(uint32_t probation_period) { hwaddr_.reset(new HWAddr()); client_id_.reset(); cltt_ = time(NULL); hostname_ = string(""); fqdn_fwd_ = false; fqdn_rev_ = false; state_ = STATE_DECLINED; valid_lft_ = probation_period; } isc::data::ElementPtr Lease4::toElement() const { // Prepare the map ElementPtr map = Element::createMap(); contextToElement(map); map->set("ip-address", Element::create(addr_.toText())); map->set("subnet-id", Element::create(static_cast(subnet_id_))); if (pool_id_) { map->set("pool-id", Element::create(static_cast(pool_id_))); } map->set("hw-address", Element::create(hwaddr_->toText(false))); if (client_id_) { map->set("client-id", Element::create(client_id_->toText())); } map->set("cltt", Element::create(cltt_)); map->set("valid-lft", Element::create(static_cast(valid_lft_))); map->set("fqdn-fwd", Element::create(fqdn_fwd_)); map->set("fqdn-rev", Element::create(fqdn_rev_)); map->set("hostname", Element::create(hostname_)); map->set("state", Element::create(static_cast(state_))); return (map); } Lease4Ptr Lease4::fromElement(const ConstElementPtr& element) { Lease4Ptr lease(new Lease4()); // Extract common lease properties into the lease. fromElementCommon(boost::dynamic_pointer_cast(lease), element); // Validate ip-address, which must be an IPv4 address. if (!lease->addr_.isV4()) { isc_throw(BadValue, "address " << lease->addr_ << " it not an IPv4 address"); } // Make sure the hw-addres is present. if (!lease->hwaddr_) { isc_throw(BadValue, "hw-address not present in the parsed lease"); } // Client identifier is IPv4 specific. ConstElementPtr client_id = element->get("client-id"); if (client_id) { if (client_id->getType() != Element::string) { isc_throw(BadValue, "client identifier is not a string in the" " parsed lease"); } try { lease->client_id_ = ClientId::fromText(client_id->stringValue()); } catch (const std::exception& ex) { isc_throw(BadValue, "invalid client identifier " << client_id->stringValue() << " in the parsed lease"); } } return (lease); } Lease6::Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr, DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid, SubnetID subnet_id, const HWAddrPtr& hwaddr, uint8_t prefixlen) : Lease(addr, valid, subnet_id, 0/*cltt*/, false, false, "", hwaddr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid), preferred_lft_(preferred), reuseable_preferred_lft_(0), extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) { if (!duid) { isc_throw(BadValue, "DUID is mandatory for an IPv6 lease"); } if (prefixlen != 128) { if (type != Lease::TYPE_PD) { isc_throw(BadValue, "prefixlen must be 128 for non prefix type"); } } cltt_ = time(NULL); current_cltt_ = cltt_; } Lease6::Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr, DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid, SubnetID subnet_id, const bool fqdn_fwd, const bool fqdn_rev, const std::string& hostname, const HWAddrPtr& hwaddr, uint8_t prefixlen) : Lease(addr, valid, subnet_id, 0/*cltt*/, fqdn_fwd, fqdn_rev, hostname, hwaddr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid), preferred_lft_(preferred), reuseable_preferred_lft_(0), extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) { if (!duid) { isc_throw(BadValue, "DUID is mandatory for an IPv6 lease"); } if (prefixlen != 128) { if (type != Lease::TYPE_PD) { isc_throw(BadValue, "prefixlen must be 128 for non prefix type"); } } cltt_ = time(NULL); current_cltt_ = cltt_; } Lease6::Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, false, false, "", HWAddrPtr()), type_(TYPE_NA), prefixlen_(0), iaid_(0), duid_(DuidPtr()), preferred_lft_(0), reuseable_preferred_lft_(0), extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) { } std::string Lease6::statesToText(const uint32_t state) { return (Lease::basicStatesToText(state)); } const std::vector& Lease6::getDuidVector() const { if (!duid_) { static std::vector empty_vec; return (empty_vec); } return (duid_->getDuid()); } void Lease6::decline(uint32_t probation_period) { hwaddr_.reset(); duid_.reset(new DUID(DUID::EMPTY())); preferred_lft_ = 0; valid_lft_ = probation_period; cltt_ = time(NULL); hostname_ = string(""); fqdn_fwd_ = false; fqdn_rev_ = false; state_ = STATE_DECLINED; } std::string Lease6::toText() const { ostringstream stream; /// @todo: print out DUID stream << "Type: " << typeToText(type_) << "(" << static_cast(type_) << ")\n" << "Address: " << addr_ << "\n" << "Prefix length: " << static_cast(prefixlen_) << "\n" << "IAID: " << iaid_ << "\n" << "Pref life: " << lifetimeToText(preferred_lft_) << "\n" << "Valid life: " << lifetimeToText(valid_lft_) << "\n" << "Cltt: " << cltt_ << "\n" << "DUID: " << (duid_?duid_->toText():"(none)") << "\n" << "Hardware addr: " << (hwaddr_?hwaddr_->toText(false):"(none)") << "\n" << "Subnet ID: " << subnet_id_ << "\n" << "Pool ID: " << pool_id_ << "\n" << "State: " << statesToText(state_) << "\n"; if (getContext()) { stream << "User context: " << getContext()->str() << "\n"; } return (stream.str()); } std::string Lease4::toText() const { ostringstream stream; stream << "Address: " << addr_ << "\n" << "Valid life: " << lifetimeToText(valid_lft_) << "\n" << "Cltt: " << cltt_ << "\n" << "Hardware addr: " << (hwaddr_ ? hwaddr_->toText(false) : "(none)") << "\n" << "Client id: " << (client_id_ ? client_id_->toText() : "(none)") << "\n" << "Subnet ID: " << subnet_id_ << "\n" << "Pool ID: " << pool_id_ << "\n" << "State: " << statesToText(state_) << "\n" << "Relay ID: " << (relay_id_.empty() ? "(none)" : str::dumpAsHex(&relay_id_[0], relay_id_.size())) << "\n" << "Remote ID: " << (remote_id_.empty() ? "(none)" : str::dumpAsHex(&remote_id_[0], remote_id_.size())) << "\n"; if (getContext()) { stream << "User context: " << getContext()->str() << "\n"; } return (stream.str()); } bool Lease4::operator==(const Lease4& other) const { return (nullOrEqualValues(hwaddr_, other.hwaddr_) && nullOrEqualValues(client_id_, other.client_id_) && addr_ == other.addr_ && subnet_id_ == other.subnet_id_ && pool_id_ == other.pool_id_ && valid_lft_ == other.valid_lft_ && current_valid_lft_ == other.current_valid_lft_ && reuseable_valid_lft_ == other.reuseable_valid_lft_ && cltt_ == other.cltt_ && current_cltt_ == other.current_cltt_ && hostname_ == other.hostname_ && fqdn_fwd_ == other.fqdn_fwd_ && fqdn_rev_ == other.fqdn_rev_ && state_ == other.state_ && nullOrEqualValues(getContext(), other.getContext())); } bool Lease6::operator==(const Lease6& other) const { return (nullOrEqualValues(duid_, other.duid_) && nullOrEqualValues(hwaddr_, other.hwaddr_) && addr_ == other.addr_ && type_ == other.type_ && prefixlen_ == other.prefixlen_ && iaid_ == other.iaid_ && preferred_lft_ == other.preferred_lft_ && reuseable_preferred_lft_ == other.reuseable_preferred_lft_ && valid_lft_ == other.valid_lft_ && current_valid_lft_ == other.current_valid_lft_ && reuseable_valid_lft_ == other.reuseable_valid_lft_ && cltt_ == other.cltt_ && current_cltt_ == other.current_cltt_ && subnet_id_ == other.subnet_id_ && pool_id_ == other.pool_id_ && hostname_ == other.hostname_ && fqdn_fwd_ == other.fqdn_fwd_ && fqdn_rev_ == other.fqdn_rev_ && state_ == other.state_ && nullOrEqualValues(getContext(), other.getContext())); } isc::data::ElementPtr Lease6::toElement() const { // Prepare the map ElementPtr map = Element::createMap(); contextToElement(map); map->set("ip-address", Element::create(addr_.toText())); map->set("type", Element::create(typeToText(type_))); if (type_ == Lease::TYPE_PD) { map->set("prefix-len", Element::create(prefixlen_)); } map->set("iaid", Element::create(static_cast(iaid_))); map->set("duid", Element::create(duid_->toText())); map->set("subnet-id", Element::create(static_cast(subnet_id_))); if (pool_id_) { map->set("pool-id", Element::create(static_cast(pool_id_))); } map->set("cltt", Element::create(cltt_)); map->set("preferred-lft", Element::create(static_cast(preferred_lft_))); map->set("valid-lft", Element::create(static_cast(valid_lft_))); map->set("fqdn-fwd", Element::create(fqdn_fwd_)); map->set("fqdn-rev", Element::create(fqdn_rev_)); map->set("hostname", Element::create(hostname_)); if (hwaddr_) { map->set("hw-address", Element::create(hwaddr_->toText(false))); } map->set("state", Element::create(static_cast(state_))); return (map); } Lease6Ptr Lease6::fromElement(const data::ConstElementPtr& element) { Lease6Ptr lease(new Lease6()); // Extract common lease properties into the lease. fromElementCommon(boost::dynamic_pointer_cast(lease), element); // Validate ip-address, which must be an IPv6 address. if (!lease->addr_.isV6()) { isc_throw(BadValue, "address " << lease->addr_ << " it not an IPv6 address"); } // lease type ConstElementPtr lease_type = element->get("type"); if (!lease_type || (lease_type->getType() != Element::string)) { isc_throw(BadValue, "type is not present in the parsed lease" " or it is not a string value"); } lease->type_ = textToType(lease_type->stringValue()); // prefix length if (lease->type_ != Lease::TYPE_PD) { lease->prefixlen_ = 128; } else { ConstElementPtr prefix_len = element->get("prefix-len"); if (!prefix_len || (prefix_len->getType() != Element::integer)) { isc_throw(BadValue, "prefix-len is not present in the parsed lease" " or it is not an integer"); } if ((prefix_len->intValue() < 1) || (prefix_len->intValue() > 128)) { isc_throw(BadValue, "prefix-len " << prefix_len->intValue() << " must be in range of [1..128]"); } lease->prefixlen_ = static_cast(prefix_len->intValue()); } // IAID ConstElementPtr iaid = element->get("iaid"); if (!iaid || (iaid->getType() != Element::integer)) { isc_throw(BadValue, "iaid is not present in the parsed lease" " or it is not an integer"); } if (iaid->intValue() < 0) { isc_throw(BadValue, "iaid " << iaid->intValue() << " must not be negative"); } lease->iaid_ = static_cast(iaid->intValue()); // DUID ConstElementPtr duid = element->get("duid"); if (!duid || (duid->getType() != Element::string)) { isc_throw(BadValue, "duid not present in the parsed lease" " or it is not a string"); } try { DUID parsed_duid = DUID::fromText(duid->stringValue()); lease->duid_.reset(new DUID(parsed_duid.getDuid())); } catch (const std::exception& ex) { isc_throw(BadValue, "invalid DUID " << duid->stringValue() << " in the parsed lease"); } // preferred lifetime ConstElementPtr preferred_lft = element->get("preferred-lft"); if (!preferred_lft || (preferred_lft->getType() != Element::integer)) { isc_throw(BadValue, "preferred-lft is not present in the parsed lease" " or is not an integer"); } if (preferred_lft->intValue() < 0) { isc_throw(BadValue, "preferred-lft " << preferred_lft->intValue() << " must not be negative"); } lease->preferred_lft_ = static_cast(preferred_lft->intValue()); return (lease); } std::ostream& operator<<(std::ostream& os, const Lease& lease) { os << lease.toText(); return (os); } } // namespace isc::dhcp } // namespace isc