// Copyright (C) 2011-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 #include #include #include #include #include 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 (auto const& it : options_) { length += it.second->len(); } return (length); } void Pkt4::pack() { if (!hwaddr_) { isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set."); } // This object is necessary to restore the packet options after performing // splitOptions4 when function scope ends. It creates a container of option // clones which are split and packed. ScopedPkt4OptionsCopy scoped_options(*this); // 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 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 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 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_); // Kea supports multiple vendor options so it needs to split received and // fused options in multiple OptionVendor instances. LibDHCP::extendVendorOptions4(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 boost::shared_ptr > type_opt = boost::dynamic_pointer_cast >(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 and // it stores message type as integer, hence // OptionInt::getValue() and OptionInt::setValue() // should be used. boost::shared_ptr > type_opt = boost::dynamic_pointer_cast >(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(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 tmp; // First print the basics tmp << "local_address=" << local_addr_ << ":" << local_port_ << ", remote_address=" << remote_addr_ << ":" << remote_port_ << "," << endl; tmp << "msg_type="; // Try to obtain message type. uint8_t msg_type = getType(); if (msg_type != DHCP_NOTYPE) { tmp << getName(msg_type) << " (" << static_cast(msg_type) << ")"; } else { // Message Type option is missing. tmp << "(missing)"; } tmp << ", trans_id=0x" << hex << transid_ << dec; if (!options_.empty()) { tmp << "," << endl << "options:"; for (auto const& opt : options_) { try { tmp << endl << opt.second->toText(2); } catch (...) { tmp << "(unknown)" << endl; } } } else { tmp << "," << endl << "message contains no options"; } return (tmp.str()); } void Pkt4::setHWAddr(uint8_t htype, uint8_t hlen, const std::vector& 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& 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=" << static_cast(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& 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(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()); } std::string Pkt4::getHWAddrLabel() const { std::ostringstream label; label << "hwaddr="; hwaddr_ ? label << hwaddr_->toText(false) : label << "(undefined)"; return (label.str()); } } // end of namespace isc::dhcp } // end of namespace isc