summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/option.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp/option.cc')
-rw-r--r--src/lib/dhcp/option.cc383
1 files changed, 383 insertions, 0 deletions
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
new file mode 100644
index 0000000..2ece006
--- /dev/null
+++ b/src/lib/dhcp/option.cc
@@ -0,0 +1,383 @@
+// Copyright (C) 2011-2022 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/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <util/io_utilities.h>
+
+#include <boost/make_shared.hpp>
+
+#include <iomanip>
+#include <list>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <string.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+OptionPtr
+Option::factory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf) {
+ return (LibDHCP::optionFactory(u, type, buf));
+}
+
+Option::Option(Universe u, uint16_t type)
+ : universe_(u), type_(type) {
+ check();
+}
+
+Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
+ : universe_(u), type_(type), data_(data) {
+ check();
+}
+
+Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : universe_(u), type_(type), data_(first, last) {
+ check();
+}
+
+Option::Option(const Option& option)
+ : universe_(option.universe_), type_(option.type_),
+ data_(option.data_), options_(),
+ encapsulated_space_(option.encapsulated_space_) {
+ option.getOptionsCopy(options_);
+}
+
+OptionPtr
+Option::create(Universe u, uint16_t type) {
+ return (boost::make_shared<Option>(u, type));
+}
+
+OptionPtr
+Option::create(Universe u, uint16_t type, const OptionBuffer& data) {
+ return (boost::make_shared<Option>(u, type, data));
+}
+
+Option&
+Option::operator=(const Option& rhs) {
+ if (&rhs != this) {
+ universe_ = rhs.universe_;
+ type_ = rhs.type_;
+ data_ = rhs.data_;
+ rhs.getOptionsCopy(options_);
+ encapsulated_space_ = rhs.encapsulated_space_;
+ }
+ return (*this);
+}
+
+OptionPtr
+Option::clone() const {
+ return (cloneInternal<Option>());
+}
+
+void
+Option::check() const {
+ if ((universe_ != V4) && (universe_ != V6)) {
+ isc_throw(BadValue, "Invalid universe type specified. "
+ << "Only V4 and V6 are allowed.");
+ }
+
+ if (universe_ == V4) {
+ if (type_ > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
+ << "For DHCPv4 allowed type range is 0..255");
+ }
+ }
+
+ // no need to check anything for DHCPv6. It allows full range (0-64k) of
+ // both types and data size.
+}
+
+void Option::pack(isc::util::OutputBuffer& buf, bool check) const {
+ // Write a header.
+ packHeader(buf, check);
+ // Write data.
+ if (!data_.empty()) {
+ buf.writeData(&data_[0], data_.size());
+ }
+ // Write sub-options.
+ packOptions(buf, check);
+}
+
+void
+Option::packHeader(isc::util::OutputBuffer& buf, bool check) const {
+ if (universe_ == V4) {
+ if (check && len() > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
+ << "At most 255 bytes are supported.");
+ }
+
+ buf.writeUint8(type_);
+ buf.writeUint8(len() - getHeaderLen());
+
+ } else {
+ buf.writeUint16(type_);
+ buf.writeUint16(len() - getHeaderLen());
+ }
+}
+
+void
+Option::packOptions(isc::util::OutputBuffer& buf, bool check) const {
+ switch (universe_) {
+ case V4:
+ LibDHCP::packOptions4(buf, options_, false, check);
+ return;
+ case V6:
+ LibDHCP::packOptions6(buf, options_);
+ return;
+ default:
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+ }
+}
+
+void Option::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ setData(begin, end);
+}
+
+void
+Option::unpackOptions(const OptionBuffer& buf) {
+ list<uint16_t> deferred;
+ switch (universe_) {
+ case V4:
+ LibDHCP::unpackOptions4(buf, getEncapsulatedSpace(),
+ options_, deferred,
+ getType() == DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ return;
+ case V6:
+ LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_);
+ return;
+ default:
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+ }
+}
+
+uint16_t Option::len() const {
+ // Returns length of the complete option (data length + DHCPv4/DHCPv6
+ // option header)
+
+ // length of the whole option is header and data stored in this option...
+ size_t length = getHeaderLen() + data_.size();
+
+ // ... and sum of lengths of all suboptions
+ for (auto const& option : options_) {
+ length += option.second->len();
+ }
+
+ // note that this is not equal to length field. This value denotes
+ // number of bytes required to store this option. length option should
+ // contain (len()-getHeaderLen()) value.
+ return (static_cast<uint16_t>(length));
+}
+
+bool
+Option::valid() const {
+ if (universe_ != V4 &&
+ universe_ != V6) {
+ return (false);
+ }
+
+ return (true);
+}
+
+OptionPtr Option::getOption(uint16_t opt_type) const {
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
+ return (x->second);
+ }
+ return OptionPtr(); // NULL
+}
+
+void
+Option::getOptionsCopy(OptionCollection& options_copy) const {
+ OptionCollection local_options;
+ for (auto const& option : options_) {
+ OptionPtr copy = option.second->clone();
+ local_options.insert(std::make_pair(option.second->getType(), copy));
+ }
+ // All options copied successfully, so assign them to the output
+ // parameter.
+ options_copy.swap(local_options);
+}
+
+bool Option::delOption(uint16_t opt_type) {
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ }
+ return (false); // option not found, can't delete
+}
+
+std::string Option::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent) << ": ";
+
+ for (unsigned int i = 0; i < data_.size(); i++) {
+ if (i) {
+ output << ":";
+ }
+ output << setfill('0') << setw(2) << hex
+ << static_cast<unsigned short>(data_[i]);
+ }
+
+ // Append suboptions.
+ output << suboptionsToText(indent + 2);
+
+ return (output.str());
+}
+
+std::string
+Option::toString() const {
+ /// @todo: Implement actual conversion in derived classes.
+ return (toText(0));
+}
+
+std::vector<uint8_t>
+Option::toBinary(const bool include_header) const {
+ OutputBuffer buf(len());
+ try {
+ // The RFC3396 adds support for long options split over multiple options
+ // using the same code.
+ pack(buf, false);
+
+ } catch (const std::exception &ex) {
+ isc_throw(OutOfRange, "unable to obtain hexadecimal representation"
+ " of option " << getType() << ": " << ex.what());
+ }
+ const uint8_t* option_data = static_cast<const uint8_t*>(buf.getData());
+
+ // Assign option data to a vector, with or without option header depending
+ // on the value of "include_header" flag.
+ std::vector<uint8_t> option_vec(option_data + (include_header ? 0 : getHeaderLen()),
+ option_data + buf.getLength());
+ return (option_vec);
+}
+
+std::string
+Option::toHexString(const bool include_header) const {
+ // Prepare binary version of the option.
+ std::vector<uint8_t> option_vec = toBinary(include_header);
+
+ // Return hexadecimal representation prepended with 0x or empty string
+ // if option has no payload and the header fields are excluded.
+ std::ostringstream s;
+ if (!option_vec.empty()) {
+ s << "0x" << encode::encodeHex(option_vec);
+ }
+ return (s.str());
+}
+
+std::string
+Option::headerToText(const int indent, const std::string& type_name) const {
+ std::stringstream output;
+ for (int i = 0; i < indent; i++)
+ output << " ";
+
+ int field_len = (getUniverse() == V4 ? 3 : 5);
+ output << "type=" << std::setw(field_len) << std::setfill('0')
+ << type_;
+
+ if (!type_name.empty()) {
+ output << "(" << type_name << ")";
+ }
+
+ output << ", len=" << std::setw(field_len) << std::setfill('0')
+ << len() - getHeaderLen();
+ return (output.str());
+}
+
+std::string
+Option::suboptionsToText(const int indent) const {
+ std::stringstream output;
+
+ if (!options_.empty()) {
+ output << "," << std::endl << "options:";
+ for (auto const& opt : options_) {
+ output << std::endl << opt.second->toText(indent);
+ }
+ }
+
+ return (output.str());
+}
+
+uint16_t
+Option::getHeaderLen() const {
+ switch (universe_) {
+ case V4:
+ return OPTION4_HDR_LEN; // header length for v4
+ case V6:
+ return OPTION6_HDR_LEN; // header length for v6
+ }
+ return 0; // should not happen
+}
+
+void Option::addOption(OptionPtr opt) {
+ options_.insert(make_pair(opt->getType(), opt));
+}
+
+uint8_t Option::getUint8() const {
+ if (data_.size() < sizeof(uint8_t) ) {
+ isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_
+ << " that has size " << data_.size());
+ }
+ return (data_[0]);
+}
+
+uint16_t Option::getUint16() const {
+ // readUint16() checks and throws OutOfRange if data_ is too small.
+ return (readUint16(&data_[0], data_.size()));
+}
+
+uint32_t Option::getUint32() const {
+ // readUint32() checks and throws OutOfRange if data_ is too small.
+ return (readUint32(&data_[0], data_.size()));
+}
+
+void Option::setUint8(uint8_t value) {
+ data_.resize(sizeof(value));
+ data_[0] = value;
+}
+
+void Option::setUint16(uint16_t value) {
+ data_.resize(sizeof(value));
+ writeUint16(value, &data_[0], data_.size());
+}
+
+void Option::setUint32(uint32_t value) {
+ data_.resize(sizeof(value));
+ writeUint32(value, &data_[0], data_.size());
+}
+
+bool Option::equals(const OptionPtr& other) const {
+ return (equals(*other));
+}
+
+bool Option::equals(const Option& other) const {
+ return ((getType() == other.getType()) &&
+ (getData() == other.getData()));
+}
+
+Option::~Option() {
+}
+
+bool Option::lenient_parsing_;
+
+} // end of isc::dhcp namespace
+} // end of isc namespace