diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
commit | 040eee1aa49b49df4698d83a05af57c220127fd1 (patch) | |
tree | f635435954e6ccde5eee9893889e24f30ca68346 /src/lib/dhcp/pkt4.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip |
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp/pkt4.cc')
-rw-r--r-- | src/lib/dhcp/pkt4.cc | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc new file mode 100644 index 0000000..216e04c --- /dev/null +++ b/src/lib/dhcp/pkt4.cc @@ -0,0 +1,602 @@ +// 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 <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_int.h> +#include <dhcp/pkt4.h> +#include <exceptions/exceptions.h> + +#include <algorithm> +#include <iostream> +#include <sstream> + +using namespace std; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace { + +/// @brief Default address used in Pkt4 constructor +const IOAddress DEFAULT_ADDRESS("0.0.0.0"); +} + +namespace isc { +namespace dhcp { + +Pkt4::Pkt4(uint8_t msg_type, uint32_t transid) + : Pkt(transid, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT), + op_(DHCPTypeToBootpType(msg_type)), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0), + ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), + giaddr_(DEFAULT_ADDRESS) { + memset(sname_, 0, MAX_SNAME_LEN); + memset(file_, 0, MAX_FILE_LEN); + + setType(msg_type); +} + +Pkt4::Pkt4(const uint8_t* data, size_t len) + : Pkt(data, len, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT), + op_(BOOTREQUEST), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0), + ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS), + giaddr_(DEFAULT_ADDRESS) { + + if (len < DHCPV4_PKT_HDR_LEN) { + isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len + << ") received, at least " << DHCPV4_PKT_HDR_LEN + << " is expected."); + } + memset(sname_, 0, MAX_SNAME_LEN); + memset(file_, 0, MAX_FILE_LEN); +} + +size_t +Pkt4::len() { + size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header + + // ... and sum of lengths of all options + for (const auto& it : options_) { + length += it.second->len(); + } + + return (length); +} + +void +Pkt4::pack() { + ScopedPkt4OptionsCopy scoped_options(*this); + if (!hwaddr_) { + isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set."); + } + + // Clear the output buffer to make sure that consecutive calls to pack() + // will not result in concatenation of multiple packet copies. + buffer_out_.clear(); + + try { + size_t hw_len = hwaddr_->hwaddr_.size(); + + buffer_out_.writeUint8(op_); + buffer_out_.writeUint8(hwaddr_->htype_); + buffer_out_.writeUint8(hw_len < MAX_CHADDR_LEN ? + hw_len : MAX_CHADDR_LEN); + buffer_out_.writeUint8(hops_); + buffer_out_.writeUint32(transid_); + buffer_out_.writeUint16(secs_); + buffer_out_.writeUint16(flags_); + buffer_out_.writeUint32(ciaddr_.toUint32()); + buffer_out_.writeUint32(yiaddr_.toUint32()); + buffer_out_.writeUint32(siaddr_.toUint32()); + buffer_out_.writeUint32(giaddr_.toUint32()); + + + if ((hw_len > 0) && (hw_len <= MAX_CHADDR_LEN)) { + // write up to 16 bytes of the hardware address (CHADDR field is 16 + // bytes long in DHCPv4 message). + buffer_out_.writeData(&hwaddr_->hwaddr_[0], + (hw_len < MAX_CHADDR_LEN ? + hw_len : MAX_CHADDR_LEN) ); + hw_len = MAX_CHADDR_LEN - hw_len; + } else { + hw_len = MAX_CHADDR_LEN; + } + + // write (len) bytes of padding + if (hw_len > 0) { + vector<uint8_t> zeros(hw_len, 0); + buffer_out_.writeData(&zeros[0], hw_len); + } + + buffer_out_.writeData(sname_, MAX_SNAME_LEN); + buffer_out_.writeData(file_, MAX_FILE_LEN); + + // write DHCP magic cookie + buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE); + + /// Create a ManagedScopedOptionsCopyContainer to handle storing and + /// restoration of copied options. + ManagedScopedOptionsCopyContainer scoped_options; + + // The RFC3396 adds support for long options split over multiple options + // using the same code. + // The long options are split in multiple CustomOption instances which + // hold the data. As a result, the option type of the newly created + // options will differ from the ones instantiated by the + // @ref OptionDefinition::optionFactory. At this stage the server should + // not do anything useful with the options beside packing. + LibDHCP::splitOptions4(options_, scoped_options.scoped_options_); + + // Call packOptions4() with parameter,"top", true. This invokes + // logic to emit the message type option first. + LibDHCP::packOptions4(buffer_out_, options_, true); + + // add END option that indicates end of options + // (End option is very simple, just a 255 octet) + buffer_out_.writeUint8(DHO_END); + } catch(const Exception& e) { + // An exception is thrown and message will be written to Logger + isc_throw(InvalidOperation, e.what()); + } +} + +void +Pkt4::unpack() { + // input buffer (used during message reception) + isc::util::InputBuffer buffer_in(&data_[0], data_.size()); + + if (buffer_in.getLength() < DHCPV4_PKT_HDR_LEN) { + isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len=" + << buffer_in.getLength() << " received, at least " + << DHCPV4_PKT_HDR_LEN << "is expected"); + } + + op_ = buffer_in.readUint8(); + uint8_t htype = buffer_in.readUint8(); + uint8_t hlen = buffer_in.readUint8(); + hops_ = buffer_in.readUint8(); + transid_ = buffer_in.readUint32(); + secs_ = buffer_in.readUint16(); + flags_ = buffer_in.readUint16(); + ciaddr_ = IOAddress(buffer_in.readUint32()); + yiaddr_ = IOAddress(buffer_in.readUint32()); + siaddr_ = IOAddress(buffer_in.readUint32()); + giaddr_ = IOAddress(buffer_in.readUint32()); + + vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0); + buffer_in.readVector(hw_addr, MAX_CHADDR_LEN); + buffer_in.readData(sname_, MAX_SNAME_LEN); + buffer_in.readData(file_, MAX_FILE_LEN); + + hw_addr.resize(hlen); + + hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype)); + + if (buffer_in.getLength() == buffer_in.getPosition()) { + // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In + // particular, it does not have magic cookie, a 4 byte sequence that + // differentiates between DHCP and RFC 951 BOOTP packets. + isc_throw(InvalidOperation, "Received BOOTP packet without vendor information extensions."); + } + + if (buffer_in.getLength() - buffer_in.getPosition() < 4) { + // there is not enough data to hold magic DHCP cookie + isc_throw(Unexpected, "Truncated or no DHCP packet."); + } + + uint32_t magic = buffer_in.readUint32(); + if (magic != DHCP_OPTIONS_COOKIE) { + isc_throw(Unexpected, "Invalid or missing DHCP magic cookie"); + } + + size_t opts_len = buffer_in.getLength() - buffer_in.getPosition(); + vector<uint8_t> opts_buffer; + + // Use readVector because a function which parses option requires + // a vector as an input. + buffer_in.readVector(opts_buffer, opts_len); + + size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_, deferred_options_, false); + + // If offset is not equal to the size and there is no DHO_END, + // then something is wrong here. We either parsed past input + // buffer (bug in our code) or we haven't parsed everything + // (received trailing garbage or truncated option). + // + // Invoking Jon Postel's law here: be conservative in what you send, and be + // liberal in what you accept. There's no easy way to log something from + // libdhcp++ library, so we just choose to be silent about remaining + // bytes. We also need to quell compiler warning about unused offset + // variable. + // + // if ((offset != size) && (opts_buffer[offset] != DHO_END)) { + // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size + // << ", were able to parse " << offset << " bytes."); + // } + (void)offset; + + // The RFC3396 adds support for multiple options using the same code fused + // into long options. + // All instances of the same option are fused together, including merging + // the suboption lists and fusing suboptions. As a result, the options will + // store more than 255 bytes of data and the regex parsers can effectively + // access the entire data. + LibDHCP::fuseOptions4(options_); + + // No need to call check() here. There are thorough tests for this + // later (see Dhcp4Srv::accept()). We want to drop the packet later, + // so we'll be able to log more detailed drop reason. +} + +uint8_t Pkt4::getType() const { + OptionPtr generic = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE); + if (!generic) { + return (DHCP_NOTYPE); + } + + // Check if Message Type is specified as OptionInt<uint8_t> + boost::shared_ptr<OptionInt<uint8_t> > type_opt = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic); + if (type_opt) { + return (type_opt->getValue()); + } + + // Try to use it as generic option + return (generic->getUint8()); +} + +void Pkt4::setType(uint8_t dhcp_type) { + OptionPtr opt = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE); + if (opt) { + + // There is message type option already, update it. It seems that + // we do have two types of objects representing message-type option. + // It would be more preferable to use only one type, but there's no + // easy way to enforce it. + // + // One is an instance of the Option class. It stores type in + // Option::data_, so Option::setUint8() and Option::getUint8() can be + // used. The other one is an instance of OptionInt<uint8_t> and + // it stores message type as integer, hence + // OptionInt<uint8_t>::getValue() and OptionInt<uint8_t>::setValue() + // should be used. + boost::shared_ptr<OptionInt<uint8_t> > type_opt = + boost::dynamic_pointer_cast<OptionInt<uint8_t> >(opt); + if (type_opt) { + type_opt->setValue(dhcp_type); + } else { + opt->setUint8(dhcp_type); + } + } else { + // There is no message type option yet, add it + opt.reset(new OptionInt<uint8_t>(Option::V4, DHO_DHCP_MESSAGE_TYPE, + dhcp_type)); + addOption(opt); + } +} + +const char* +Pkt4::getName(const uint8_t type) { + static const char* DHCPDISCOVER_NAME = "DHCPDISCOVER"; + static const char* DHCPOFFER_NAME = "DHCPOFFER"; + static const char* DHCPREQUEST_NAME = "DHCPREQUEST"; + static const char* DHCPDECLINE_NAME = "DHCPDECLINE"; + static const char* DHCPACK_NAME = "DHCPACK"; + static const char* DHCPNAK_NAME = "DHCPNAK"; + static const char* DHCPRELEASE_NAME = "DHCPRELEASE"; + static const char* DHCPINFORM_NAME = "DHCPINFORM"; + static const char* DHCPLEASEQUERY_NAME = "DHCPLEASEQUERY"; + static const char* DHCPLEASEUNASSIGNED_NAME = "DHCPLEASEUNASSIGNED"; + static const char* DHCPLEASEUNKNOWN_NAME = "DHCPLEASEUNKNOWN"; + static const char* DHCPLEASEACTIVE_NAME = "DHCPLEASEACTIVE"; + static const char* DHCPBULKLEASEQUERY_NAME = "DHCPBULKLEASEQUERY"; + static const char* DHCPLEASEQUERYDONE_NAME = "DHCPLEASEQUERYDONE"; + static const char* DHCPLEASEQUERYSTATUS_NAME = "DHCPLEASEQUERYSTATUS"; + static const char* DHCPTLS_NAME = "DHCPTLS"; + static const char* UNKNOWN_NAME = "UNKNOWN"; + + switch (type) { + case DHCPDISCOVER: + return (DHCPDISCOVER_NAME); + + case DHCPOFFER: + return (DHCPOFFER_NAME); + + case DHCPREQUEST: + return (DHCPREQUEST_NAME); + + case DHCPDECLINE: + return (DHCPDECLINE_NAME); + + case DHCPACK: + return (DHCPACK_NAME); + + case DHCPNAK: + return (DHCPNAK_NAME); + + case DHCPRELEASE: + return (DHCPRELEASE_NAME); + + case DHCPINFORM: + return (DHCPINFORM_NAME); + + case DHCPLEASEQUERY: + return (DHCPLEASEQUERY_NAME); + + case DHCPLEASEUNASSIGNED: + return (DHCPLEASEUNASSIGNED_NAME); + + case DHCPLEASEUNKNOWN: + return (DHCPLEASEUNKNOWN_NAME); + + case DHCPLEASEACTIVE: + return (DHCPLEASEACTIVE_NAME); + + case DHCPBULKLEASEQUERY: + return (DHCPBULKLEASEQUERY_NAME); + + case DHCPLEASEQUERYDONE: + return (DHCPLEASEQUERYDONE_NAME); + + case DHCPLEASEQUERYSTATUS: + return (DHCPLEASEQUERYSTATUS_NAME); + + case DHCPTLS: + return (DHCPTLS_NAME); + + default: + ; + } + return (UNKNOWN_NAME); +} + +const char* +Pkt4::getName() const { + // getType() is now exception safe. Even if there's no option 53 (message + // type), it now returns 0 rather than throw. getName() is able to handle + // 0 and unknown message types. + return (Pkt4::getName(getType())); +} + +std::string +Pkt4::getLabel() const { + + /// @todo If and when client id is extracted into Pkt4, this method should + /// use the instance member rather than fetch it every time. + std::string suffix; + ClientIdPtr client_id; + OptionPtr client_opt = getNonCopiedOption(DHO_DHCP_CLIENT_IDENTIFIER); + if (client_opt) { + try { + client_id = ClientIdPtr(new ClientId(client_opt->getData())); + } catch (...) { + // ClientId may throw if the client-id is too short. + suffix = " (malformed client-id)"; + } + } + + std::ostringstream label; + try { + label << makeLabel(hwaddr_, client_id, transid_); + } catch (...) { + // This should not happen with the current code, but we may add extra + // sanity checks in the future that would possibly throw if + // the hwaddr length is 0. + label << " (malformed hw address)"; + } + + label << suffix; + return (label.str()); +} + +std::string +Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id, + const uint32_t transid) { + // Create label with HW address and client identifier. + stringstream label; + label << makeLabel(hwaddr, client_id); + + // Append transaction id. + label << ", tid=0x" << hex << transid << dec; + + return label.str(); +} + +std::string +Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id) { + stringstream label; + label << "[" << (hwaddr ? hwaddr->toText() : "no hwaddr info") + << "], cid=[" << (client_id ? client_id->toText() : "no info") + << "]"; + + return label.str(); +} + +std::string +Pkt4::toText() const { + stringstream output; + output << "local_address=" << local_addr_ << ":" << local_port_ + << ", remote_address=" << remote_addr_ + << ":" << remote_port_ << ", msg_type="; + + // Try to obtain message type. + uint8_t msg_type = getType(); + if (msg_type != DHCP_NOTYPE) { + output << getName(msg_type) << " (" << static_cast<int>(msg_type) << ")"; + } else { + // Message Type option is missing. + output << "(missing)"; + } + + output << ", transid=0x" << hex << transid_ << dec; + + if (!options_.empty()) { + output << "," << std::endl << "options:"; + for (const auto& opt : options_) { + try { + output << std::endl << opt.second->toText(2); + } catch (...) { + output << "(unknown)" << std::endl; + } + } + + } else { + output << ", message contains no options"; + } + + return (output.str()); +} + +void +Pkt4::setHWAddr(uint8_t htype, uint8_t hlen, + const std::vector<uint8_t>& mac_addr) { + setHWAddrMember(htype, hlen, mac_addr, hwaddr_); +} + +void +Pkt4::setHWAddr(const HWAddrPtr& addr) { + if (!addr) { + isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL" + << " is forbidden"); + } + hwaddr_ = addr; +} + +void +Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr, + HWAddrPtr& hw_addr) { + /// @todo Rewrite this once support for client-identifier option + /// is implemented (ticket 1228?) + if (hlen > MAX_CHADDR_LEN) { + isc_throw(OutOfRange, "Hardware address (len=" << hlen + << " too long. Max " << MAX_CHADDR_LEN << " supported."); + + } else if (mac_addr.empty() && (hlen > 0) ) { + isc_throw(OutOfRange, "Invalid HW Address specified"); + } + + /// @todo: what if mac_addr.size() doesn't match hlen? + /// We would happily copy over hardware address that is possibly + /// too long or doesn't match hlen value. + hw_addr.reset(new HWAddr(mac_addr, htype)); +} + +void +Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen, + const std::vector<uint8_t>& mac_addr) { + setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_); +} + +void +Pkt4::setLocalHWAddr(const HWAddrPtr& addr) { + if (!addr) { + isc_throw(BadValue, "Setting local HW address to NULL is" + << " forbidden."); + } + local_hwaddr_ = addr; +} + +void +Pkt4::setSname(const uint8_t* sname, size_t snameLen /*= MAX_SNAME_LEN*/) { + if (snameLen > MAX_SNAME_LEN) { + isc_throw(OutOfRange, "sname field (len=" << snameLen + << ") too long, Max " << MAX_SNAME_LEN << " supported."); + + } else if (sname == NULL) { + isc_throw(InvalidParameter, "Invalid sname specified"); + } + + std::copy(sname, (sname + snameLen), sname_); + if (snameLen < MAX_SNAME_LEN) { + std::fill((sname_ + snameLen), (sname_ + MAX_SNAME_LEN), 0); + } + + // No need to store snameLen as any empty space is filled with 0s +} + +void +Pkt4::setFile(const uint8_t* file, size_t fileLen /*= MAX_FILE_LEN*/) { + if (fileLen > MAX_FILE_LEN) { + isc_throw(OutOfRange, "file field (len=" << fileLen + << ") too long, Max " << MAX_FILE_LEN << " supported."); + + } else if (file == NULL) { + isc_throw(InvalidParameter, "Invalid file name specified"); + } + + std::copy(file, (file + fileLen), file_); + if (fileLen < MAX_FILE_LEN) { + std::fill((file_ + fileLen), (file_ + MAX_FILE_LEN), 0); + } + + // No need to store fileLen as any empty space is filled with 0s +} + +uint8_t +// cppcheck-suppress unusedFunction +Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) { + switch (dhcpType) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + case DHCPLEASEQUERY: + case DHCPBULKLEASEQUERY: + return (BOOTREQUEST); + + case DHCPACK: + case DHCPNAK: + case DHCPOFFER: + case DHCPLEASEUNASSIGNED: + case DHCPLEASEUNKNOWN: + case DHCPLEASEACTIVE: + case DHCPLEASEQUERYDONE: + return (BOOTREPLY); + + default: + isc_throw(OutOfRange, "Invalid message type: " + << static_cast<int>(dhcpType) ); + } +} + +uint8_t +Pkt4::getHtype() const { + if (!hwaddr_) { + return (HTYPE_UNDEFINED); + } + return (hwaddr_->htype_); +} + +uint8_t +Pkt4::getHlen() const { + if (!hwaddr_) { + return (0); + } + uint8_t len = hwaddr_->hwaddr_.size(); + return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN); +} + +void +Pkt4::addOption(const OptionPtr& opt) { + // Check for uniqueness (DHCPv4 options must be unique) + if (getNonCopiedOption(opt->getType())) { + isc_throw(BadValue, "Option " << opt->getType() + << " already present in this message."); + } + + Pkt::addOption(opt); +} + +bool +Pkt4::isRelayed() const { + return (!giaddr_.isV4Zero() && !giaddr_.isV4Bcast()); +} + +} // end of namespace isc::dhcp +} // end of namespace isc |