From 52c021ee0b0c6ad2128ed550c694aad0d11d4c3f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 16:53:22 +0200 Subject: Adding upstream version 2.5.7. Signed-off-by: Daniel Baumann --- src/lib/dhcp/option_classless_static_route.cc | 274 ++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 src/lib/dhcp/option_classless_static_route.cc (limited to 'src/lib/dhcp/option_classless_static_route.cc') diff --git a/src/lib/dhcp/option_classless_static_route.cc b/src/lib/dhcp/option_classless_static_route.cc new file mode 100644 index 0000000..d6cc494 --- /dev/null +++ b/src/lib/dhcp/option_classless_static_route.cc @@ -0,0 +1,274 @@ +// Copyright (C) 2023-2024 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 + +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +OptionClasslessStaticRoute::OptionClasslessStaticRoute(OptionBufferConstIter begin, + OptionBufferConstIter end, + bool convenient_notation) + : Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0), + convenient_notation_(convenient_notation) { + unpack(begin, end); +} + +OptionPtr +OptionClasslessStaticRoute::clone() const { + return (cloneInternal()); +} + +void +OptionClasslessStaticRoute::pack(isc::util::OutputBuffer& buf, bool check) const { + // Header = option code and length. + packHeader(buf, check); + for (auto const& route : static_routes_) { + // 1-5 octets of destination descriptor + auto dest = encodeDestinationDescriptor(route); + buf.writeData(&dest[0], dest.size()); + // IP address of the router + buf.writeUint32(std::get<2>(route).toUint32()); + } +} + +void +OptionClasslessStaticRoute::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + // Classless Static route option data must contain at least 5 octets. + // 1 octet - shortest possible destination descriptor (0x00) + 4 octets router IPv4 addr. + if (distance(begin, end) < 5) { + isc_throw(OutOfRange, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid length=" << distance(begin, end) + << ", must be at least 5."); + } + + if (convenient_notation_) { + // As an alternative to the binary format, + // we provide convenience option definition as a string in format: + // subnet1 - router1 IP addr, subnet2 - router2 IP addr, ... + // e.g.: + // 10.0.0.0/8 - 10.2.3.1, 10.229.0.128/25 - 10.1.0.3, ... + // where destination descriptors will be encoded as per RFC3442. + std::string config_txt = std::string(begin, end); + parseConfigData(config_txt); + } else { + parseWireData(begin, end); + } + + calcDataLen(); +} + +std::string +OptionClasslessStaticRoute::toText(int indent) const { + std::ostringstream stream; + std::string in(indent, ' '); // base indentation + stream << in << "type=" << type_ << "(CLASSLESS_STATIC_ROUTE), " + << "len=" << (len() - getHeaderLen()); + int i = 0; + for (auto const& route : static_routes_) { + stream << ", Route " << ++i << " (subnet " << std::get<0>(route).toText() << "/" + << static_cast(std::get<1>(route)) << ", router IP " + << std::get<2>(route).toText() << ")"; + } + + return (stream.str()); +} + +uint16_t +OptionClasslessStaticRoute::len() const { + uint16_t len = getHeaderLen(); + len += data_len_; + return (len); +} + +std::vector +OptionClasslessStaticRoute::encodeDestinationDescriptor(const StaticRouteTuple& route) { + // Encoding as per RFC3442 + const std::vector& subnet = std::get<0>(route).toBytes(); + const uint8_t& mask_width = std::get<1>(route); + + std::vector res; + res.push_back(mask_width); + if (mask_width == 0) { + // there are no significant octets, destination descriptor is 0 value - one octet long + return (res); + } + + uint8_t significant_octets = calcSignificantOctets(mask_width); + res.insert(res.end(), subnet.begin(), subnet.begin() + significant_octets); + + return (res); +} + +uint8_t +OptionClasslessStaticRoute::calcSignificantOctets(const uint8_t& mask_width) { + return ((mask_width + 7) / 8); +} + +void +OptionClasslessStaticRoute::calcDataLen() { + uint16_t len = 0; + for (auto const& route : static_routes_) { + // 1-5 octets of destination descriptor + len += calcSignificantOctets(std::get<1>(route)) + 1; + // IP address of the router + len += V4ADDRESS_LEN; + } + + data_len_ = len; +} + +void +OptionClasslessStaticRoute::parseWireData(OptionBufferConstIter begin, OptionBufferConstIter end) { + while (begin != end) { + // check for truncated data for each static route + if (distance(begin, end) < 5) { + isc_throw(OutOfRange, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid length=" << distance(begin, end) + << ", must be at least 5."); + } + + // 1st octet is a width of subnet mask + uint8_t mask_width = *begin; + if (mask_width > 32) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid value, provided width of subnet mask " + << static_cast(mask_width) << " is not valid."); + } + + uint8_t significant_octets = calcSignificantOctets(mask_width); + ++begin; + + // once we know haw many significant octets there are, check for truncated data again + if (distance(begin, end) < (significant_octets + V4ADDRESS_LEN)) { + isc_throw(OutOfRange, + "DHCPv4 OptionClasslessStaticRoute " << type_ << " is truncated."); + } + + // following octets are significant octets of the subnet nr + uint32_t subnet_octets; + IOAddress subnet_nr = asiolink::IOAddress::IPV4_ZERO_ADDRESS(); + + switch (significant_octets) { + case 0: + // no-op - this is 0.0.0.0/0 subnet + break; + case 1: + subnet_octets = *begin; + subnet_nr = IOAddress(subnet_octets << 24); + break; + case 2: + subnet_octets = readUint16(&(*begin), distance(begin, end)); + subnet_nr = IOAddress(subnet_octets << 16); + break; + case 3: + // we are reading one octet too much in this case, + // but since we did check for truncated data before, + // we are safe do so and mask 4th octet with zeros + subnet_octets = readUint32(&(*begin), distance(begin, end)); + subnet_nr = IOAddress(subnet_octets & 0xFFFFFF00); + break; + case 4: + subnet_octets = readUint32(&(*begin), distance(begin, end)); + subnet_nr = IOAddress(subnet_octets); + break; + } + + begin += significant_octets; + + // last comes router IPv4 address + IOAddress router_addr = IOAddress(readUint32(&(*begin), distance(begin, end))); + begin += V4ADDRESS_LEN; + + StaticRouteTuple route = std::make_tuple(subnet_nr, mask_width, router_addr); + static_routes_.push_back(route); + } +} + +void +OptionClasslessStaticRoute::parseConfigData(const std::string& config_txt) { + // this option allows more than one static route, so let's separate them using comma + std::vector tokens = str::tokens(config_txt, std::string(",")); + for (auto const& route_str : tokens) { + std::vector parts = str::tokens(str::trim(route_str), std::string("-")); + if (parts.size() != 2) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ + << " has invalid value, option definition must" + " have comma separated routes formatted as in " + "example: 10.229.0.128/25 - 10.229.0.1"); + } + + std::string txt_subnet_prefix = str::trim(parts[0]); + + // Is this prefix/len notation? + size_t pos = txt_subnet_prefix.find('/'); + if (pos == std::string::npos) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid value, provided IPv4 prefix " + << txt_subnet_prefix << " is not valid."); + } + + std::string txt_subnet_addr = txt_subnet_prefix.substr(0, pos); + IOAddress subnet_addr = IOAddress("::"); + try { + subnet_addr = IOAddress(txt_subnet_addr); + if (!subnet_addr.isV4()) { + isc_throw(BadValue, "This is not IPv4 address."); + } + } catch (const std::exception& e) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid value, provided subnet_addr " + << txt_subnet_addr << " is not a valid IPv4 address. " + << "Error: " << e.what()); + } + + std::string txt_prefix_len = txt_subnet_prefix.substr(pos + 1); + int16_t prefix_len = 0; + try { + // We should be able to lexically cast IPv4 prefix len to short int. + // After that len<=32 check is also required. + prefix_len = boost::lexical_cast(txt_prefix_len); + if (prefix_len > 32) { + isc_throw(BadValue, "Provided IPv4 prefix len is out of 0-32 range."); + } + } catch (const std::exception& e) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid value, provided prefix len " + << txt_prefix_len << " is not valid. " + << "Error: " << e.what()); + } + + IOAddress router_addr = IOAddress("::"); + std::string txt_router = str::trim(parts[1]); + try { + router_addr = IOAddress(txt_router); + if (!router_addr.isV4()) { + isc_throw(BadValue, "This is not IPv4 address."); + } + } catch (const std::exception& e) { + isc_throw(BadValue, "DHCPv4 OptionClasslessStaticRoute " + << type_ << " has invalid value, provided router address " + << txt_router << " is not a valid IPv4 address. " + << "Error: " << e.what()); + } + + StaticRouteTuple route = std::make_tuple(subnet_addr, prefix_len, router_addr); + static_routes_.push_back(route); + } +} + +} // namespace dhcp +} // namespace isc -- cgit v1.2.3