summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/pkt6.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp/pkt6.cc')
-rw-r--r--src/lib/dhcp/pkt6.cc936
1 files changed, 936 insertions, 0 deletions
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
new file mode 100644
index 0000000..55d3b5f
--- /dev/null
+++ b/src/lib/dhcp/pkt6.cc
@@ -0,0 +1,936 @@
+// 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/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <util/io_utilities.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
+
+#include <iterator>
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+/// @brief Default address used in Pkt6 constructor
+const IOAddress DEFAULT_ADDRESS6("::");
+
+namespace isc {
+namespace dhcp {
+
+Pkt6::RelayInfo::RelayInfo()
+ : msg_type_(0), hop_count_(0), linkaddr_(DEFAULT_ADDRESS6),
+ peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
+}
+
+std::string Pkt6::RelayInfo::toText() const {
+ stringstream tmp;
+ tmp << "msg-type=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), hop-count=" << static_cast<int>(hop_count_) << "," << endl
+ << "link-address=" << linkaddr_.toText()
+ << ", peer-address=" << peeraddr_.toText() << ", "
+ << options_.size() << " option(s)" << endl;
+ for (const auto& option : options_) {
+ tmp << option.second->toText() << endl;
+ }
+ return (tmp.str());
+}
+
+Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
+ : Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
+ msg_type_(0) {
+}
+
+Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/)
+ : Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
+ msg_type_(msg_type) {
+}
+
+size_t Pkt6::len() {
+ if (relay_info_.empty()) {
+ return (directLen());
+ } else {
+ // Unfortunately we need to re-calculate relay size every time, because
+ // we need to make sure that once a new option is added, its extra size
+ // is reflected in Pkt6::len().
+ calculateRelaySizes();
+ return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
+ }
+}
+
+void
+Pkt6::prepareGetAnyRelayOption(const RelaySearchOrder& order,
+ int& start, int& end, int& direction) const {
+ switch (order) {
+ case RELAY_SEARCH_FROM_CLIENT:
+ // Search backwards
+ start = relay_info_.size() - 1;
+ end = 0;
+ direction = -1;
+ break;
+ case RELAY_SEARCH_FROM_SERVER:
+ // Search forward
+ start = 0;
+ end = relay_info_.size() - 1;
+ direction = 1;
+ break;
+ case RELAY_GET_FIRST:
+ // Look at the innermost relay only
+ start = relay_info_.size() - 1;
+ end = start;
+ direction = 1;
+ break;
+ case RELAY_GET_LAST:
+ // Look at the outermost relay only
+ start = 0;
+ end = 0;
+ direction = 1;
+ }
+}
+
+
+OptionPtr
+Pkt6::getNonCopiedAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order) const {
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getNonCopiedRelayOption(option_code, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order) {
+
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getRelayOption(option_code, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getNonCopiedRelayOption(const uint16_t opt_type,
+ const uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type);
+ if (x != relay_info_[relay_level].options_.end()) {
+ return (x->second);
+ }
+
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ OptionCollection::iterator x = relay_info_[relay_level].options_.find(opt_type);
+ if (x != relay_info_[relay_level].options_.end()) {
+ if (copy_retrieved_options_) {
+ OptionPtr relay_option_copy = x->second->clone();
+ x->second = relay_option_copy;
+ }
+ return (x->second);
+ }
+
+ return (OptionPtr());
+}
+
+const isc::asiolink::IOAddress&
+Pkt6::getRelay6LinkAddress(uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ return (relay_info_[relay_level].linkaddr_);
+}
+
+const isc::asiolink::IOAddress&
+Pkt6::getRelay6PeerAddress(uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ return (relay_info_[relay_level].peeraddr_);
+}
+
+uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
+ uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+ + Option::OPTION6_HDR_LEN; // header of the relay-msg option
+
+ for (const auto& opt : relay.options_) {
+ len += (opt.second)->len();
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::calculateRelaySizes() {
+
+ uint16_t len = directLen(); // start with length of all options
+
+ for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
+ relay_info_[relay_index - 1].relay_msg_len_ = len;
+ len += getRelayOverhead(relay_info_[relay_index - 1]);
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::directLen() const {
+ uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
+
+ for (const auto& it : options_) {
+ length += it.second->len();
+ }
+
+ return (length);
+}
+
+
+void
+Pkt6::pack() {
+ switch (proto_) {
+ case UDP:
+ packUDP();
+ break;
+ case TCP:
+ packTCP();
+ break;
+ default:
+ isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+ }
+}
+
+void
+Pkt6::packUDP() {
+ try {
+ // Make sure that the buffer is empty before we start writing to it.
+ buffer_out_.clear();
+
+ // is this a relayed packet?
+ if (!relay_info_.empty()) {
+
+ // calculate size needed for each relay (if there is only one relay,
+ // then it will be equal to "regular" length + relay-forw header +
+ // size of relay-msg option header + possibly size of interface-id
+ // option (if present). If there is more than one relay, the whole
+ // process is called iteratively for each relay.
+ calculateRelaySizes();
+
+ // Now for each relay, we need to...
+ for (vector<RelayInfo>::iterator relay = relay_info_.begin();
+ relay != relay_info_.end(); ++relay) {
+
+ // build relay-forw/relay-repl header (see RFC 8415, section 9)
+ buffer_out_.writeUint8(relay->msg_type_);
+ buffer_out_.writeUint8(relay->hop_count_);
+ buffer_out_.writeData(&(relay->linkaddr_.toBytes()[0]),
+ isc::asiolink::V6ADDRESS_LEN);
+ buffer_out_.writeData(&relay->peeraddr_.toBytes()[0],
+ isc::asiolink::V6ADDRESS_LEN);
+
+ // store every option in this relay scope. Usually that will be
+ // only interface-id, but occasionally other options may be
+ // present here as well (vendor-opts for Cable modems,
+ // subscriber-id, remote-id, options echoed back from Echo
+ // Request Option, etc.)
+ for (const auto& opt : relay->options_) {
+ (opt.second)->pack(buffer_out_);
+ }
+
+ // and include header relay-msg option. Its payload will be
+ // generated in the next iteration (if there are more relays)
+ // or outside the loop (if there are no more relays and the
+ // payload is a direct message)
+ buffer_out_.writeUint16(D6O_RELAY_MSG);
+ buffer_out_.writeUint16(relay->relay_msg_len_);
+ }
+
+ }
+
+ // DHCPv6 header: message-type (1 octet) + transaction id (3 octets)
+ buffer_out_.writeUint8(msg_type_);
+ // store 3-octet transaction-id
+ buffer_out_.writeUint8( (transid_ >> 16) & 0xff );
+ buffer_out_.writeUint8( (transid_ >> 8) & 0xff );
+ buffer_out_.writeUint8( (transid_) & 0xff );
+
+ // the rest are options
+ LibDHCP::packOptions6(buffer_out_, options_);
+ }
+ catch (const Exception& e) {
+ // An exception is thrown and message will be written to Logger
+ isc_throw(InvalidOperation, e.what());
+ }
+}
+
+void
+Pkt6::packTCP() {
+ /// TODO Implement this function.
+ isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
+ " not implemented yet.");
+}
+
+void
+Pkt6::unpack() {
+ switch (proto_) {
+ case UDP:
+ return unpackUDP();
+ case TCP:
+ return unpackTCP();
+ default:
+ isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+ }
+}
+
+void
+Pkt6::unpackUDP() {
+ if (data_.size() < 4) {
+ isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
+ << data_.size() << ", DHCPv6 header alone has 4 bytes.");
+ }
+ msg_type_ = data_[0];
+ switch (msg_type_) {
+ case DHCPV6_SOLICIT:
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_REQUEST:
+ case DHCPV6_CONFIRM:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ case DHCPV6_REPLY:
+ case DHCPV6_DECLINE:
+ case DHCPV6_RECONFIGURE:
+ case DHCPV6_INFORMATION_REQUEST:
+ case DHCPV6_DHCPV4_QUERY:
+ case DHCPV6_DHCPV4_RESPONSE:
+ default: // assume that unknown messages are not using relay format
+ {
+ return (unpackMsg(data_.begin(), data_.end()));
+ }
+ case DHCPV6_RELAY_FORW:
+ case DHCPV6_RELAY_REPL:
+ return (unpackRelayMsg());
+ }
+}
+
+void
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ size_t size = std::distance(begin, end);
+ if (size < 4) {
+ // truncated message (less than 4 bytes)
+ isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
+ << data_.size() << ", DHCPv6 header alone has 4 bytes.");
+ }
+
+ msg_type_ = *begin++;
+
+ transid_ = ( (*begin++) << 16 ) +
+ ((*begin++) << 8) + (*begin++);
+ transid_ = transid_ & 0xffffff;
+
+ // See below about invoking Postel's law, as we aren't using
+ // size we don't need to update it. If we do so in the future
+ // perhaps for stats gathering we can uncomment this.
+ // size -= sizeof(uint32_t); // We just parsed 4 bytes header
+
+ OptionBuffer opt_buffer(begin, end);
+
+ // If custom option parsing function has been set, use this function
+ // to parse options. Otherwise, use standard function from libdhcp.
+ size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_);
+
+ // If offset is not equal to the size, 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) {
+ // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
+ // << ", were able to parse " << offset << " bytes.");
+ // }
+ (void)offset;
+}
+
+void
+Pkt6::unpackRelayMsg() {
+
+ // we use offset + bufsize, because we want to avoid creating unnecessary
+ // copies. There may be up to 32 relays. While using InputBuffer would
+ // be probably a bit cleaner, copying data up to 32 times is unacceptable
+ // price here. Hence a single buffer with offsets and lengths.
+ size_t bufsize = data_.size();
+ size_t offset = 0;
+
+ while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
+
+ RelayInfo relay;
+
+ size_t relay_msg_offset = 0;
+ size_t relay_msg_len = 0;
+
+ // parse fixed header first (first 34 bytes)
+ relay.msg_type_ = data_[offset++];
+ relay.hop_count_ = data_[offset++];
+ relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
+
+ // parse the rest as options
+ OptionBuffer opt_buffer(&data_[offset], &data_[offset] + bufsize);
+
+ // If custom option parsing function has been set, use this function
+ // to parse options. Otherwise, use standard function from libdhcp.
+ LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, relay.options_,
+ &relay_msg_offset, &relay_msg_len);
+
+ /// @todo: check that each option appears at most once
+ //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
+ //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
+ //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
+
+ if (relay_msg_offset == 0 || relay_msg_len == 0) {
+ isc_throw(BadValue, "Mandatory relay-msg option missing");
+ }
+
+ // store relay information parsed so far
+ addRelayInfo(relay);
+
+ /// @todo: implement ERO (Echo Request Option, RFC 4994) here
+
+ if (relay_msg_len >= bufsize) {
+ // length of the relay_msg option extends beyond end of the message
+ isc_throw(Unexpected, "Relay-msg option is truncated.");
+ }
+ uint8_t inner_type = data_[offset + relay_msg_offset];
+ offset += relay_msg_offset; // offset is relative
+ bufsize = relay_msg_len; // length is absolute
+
+ if ( (inner_type != DHCPV6_RELAY_FORW) &&
+ (inner_type != DHCPV6_RELAY_REPL)) {
+ // Ok, the inner message is not encapsulated, let's decode it
+ // directly
+ return (unpackMsg(data_.begin() + offset, data_.begin() + offset
+ + relay_msg_len));
+ }
+
+ // Oh well, there's inner relay-forw or relay-repl inside. Let's
+ // unpack it as well. The next loop iteration will take care
+ // of that.
+ }
+
+ if ( (offset == data_.size()) && (bufsize == 0) ) {
+ // message has been parsed completely
+ return;
+ }
+
+ /// @todo: log here that there are additional unparsed bytes
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+ if (relay_info_.size() > HOP_COUNT_LIMIT) {
+ isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
+ }
+
+ /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
+ relay_info_.push_back(relay);
+}
+
+void
+Pkt6::unpackTCP() {
+ isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
+ "not implemented yet.");
+}
+
+HWAddrPtr
+Pkt6::getMACFromDUID() {
+ HWAddrPtr mac;
+ OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ return (mac);
+ }
+
+ uint8_t hlen = opt_duid->getData().size();
+ if (!hlen) {
+ return (mac);
+ }
+ vector<uint8_t> hw_addr(hlen, 0);
+ std::vector<unsigned char> duid_data = opt_duid->getData();
+
+ // Read the first two bytes. That duid type.
+ uint16_t duid_type = util::readUint16(&duid_data[0], duid_data.size());
+
+ switch (duid_type) {
+ case DUID::DUID_LL:
+ {
+ // 2 bytes of duid type, 2 bytes of hardware type and at least
+ // 1 byte of actual identification
+ if (duid_data.size() >= 5) {
+ uint16_t hwtype = util::readUint16(&duid_data[2],
+ duid_data.size() - 2);
+ mac.reset(new HWAddr(&duid_data[4], duid_data.size() - 4, hwtype));
+ }
+ break;
+ }
+ case DUID::DUID_LLT:
+ {
+ // 2 bytes of duid type, 2 bytes of hardware, 4 bytes for timestamp,
+ // and at least 1 byte of actual identification
+ if (duid_data.size() >= 9) {
+ uint16_t hwtype = util::readUint16(&duid_data[2],
+ duid_data.size() - 2);
+ mac.reset(new HWAddr(&duid_data[8], duid_data.size() - 8, hwtype));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (mac) {
+ mac->source_ = HWAddr::HWADDR_SOURCE_DUID;
+ }
+
+ return (mac);
+}
+
+std::string
+Pkt6::makeLabel(const DuidPtr duid, const uint32_t transid,
+ const HWAddrPtr& hwaddr) {
+ // Create label with DUID and HW address.
+ std::stringstream label;
+ label << makeLabel(duid, hwaddr);
+
+ // Append transaction id.
+ label << ", tid=0x" << std::hex << transid << std::dec;
+
+ return (label.str());
+}
+
+std::string
+Pkt6::makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr) {
+ std::stringstream label;
+ // DUID should be present at all times, so explicitly inform when
+ // it is no present (no info).
+ label << "duid=[" << (duid ? duid->toText() : "no info")
+ << "]";
+
+ // HW address is typically not carried in the DHCPv6 messages
+ // and can be extracted using various, but not fully reliable,
+ // techniques. If it is not present, don't print anything.
+ if (hwaddr) {
+ label << ", [" << hwaddr->toText() << "]";
+ }
+
+ return (label.str());
+}
+
+std::string
+Pkt6::getLabel() const {
+ /// @todo Do not print HW address as it is unclear how it should
+ /// be retrieved if there is no access to user configuration which
+ /// specifies the order of various techniques to be used to retrieve
+ /// it.
+ return (makeLabel(getClientId(), getTransid(), HWAddrPtr()));}
+
+std::string
+Pkt6::toText() const {
+ stringstream tmp;
+
+ // First print the basics
+ tmp << "localAddr=[" << local_addr_ << "]:" << local_port_
+ << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl;
+ tmp << "msgtype=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), transid=0x" <<
+ hex << transid_ << dec << endl;
+
+ // Then print the options
+ for (const auto& opt : options_) {
+ tmp << opt.second->toText() << std::endl;
+ }
+
+ // Finally, print the relay information (if present)
+ if (!relay_info_.empty()) {
+ tmp << relay_info_.size() << " relay(s):" << endl;
+ int cnt = 0;
+ for (const auto& relay : relay_info_) {
+ tmp << "relay[" << cnt++ << "]: " << relay.toText();
+ }
+ } else {
+ tmp << "No relays traversed." << endl;
+ }
+ return tmp.str();
+}
+
+DuidPtr
+Pkt6::getClientId() const {
+ OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
+ try {
+ // This will throw if the DUID length is larger than 128 bytes
+ // or is too short.
+ return (opt_duid ? DuidPtr(new DUID(opt_duid->getData())) : DuidPtr());
+ } catch (...) {
+ // Do nothing. This method is used only by getLabel(), which is
+ // used for logging purposes. We should not throw, but rather
+ // report no DUID. We should not log anything, as we're in the
+ // process of logging something for this packet. So the only
+ // choice left is to return an empty pointer.
+ }
+ return (DuidPtr());
+}
+
+isc::dhcp::OptionCollection
+Pkt6::getNonCopiedOptions(const uint16_t opt_type) const {
+ std::pair<OptionCollection::const_iterator,
+ OptionCollection::const_iterator> range = options_.equal_range(opt_type);
+ return (OptionCollection(range.first, range.second));
+}
+
+isc::dhcp::OptionCollection
+Pkt6::getOptions(const uint16_t opt_type) {
+ OptionCollection options_copy;
+
+ std::pair<OptionCollection::iterator,
+ OptionCollection::iterator> range = options_.equal_range(opt_type);
+ // If options should be copied on retrieval, we should now iterate over
+ // matching options, copy them and replace the original ones with new
+ // instances.
+ if (copy_retrieved_options_) {
+ for (OptionCollection::iterator opt_it = range.first;
+ opt_it != range.second; ++opt_it) {
+ OptionPtr option_copy = opt_it->second->clone();
+ opt_it->second = option_copy;
+ }
+ }
+ // Finally, return updated options. This can also be empty in some cases.
+ return (OptionCollection(range.first, range.second));
+}
+
+const char*
+Pkt6::getName(const uint8_t type) {
+ static const char* ADVERTISE = "ADVERTISE";
+ static const char* CONFIRM = "CONFIRM";
+ static const char* DECLINE = "DECLINE";
+ static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+ static const char* LEASEQUERY = "LEASEQUERY";
+ static const char* LEASEQUERY_REPLY = "LEASEQUERY_REPLY";
+ static const char* REBIND = "REBIND";
+ static const char* RECONFIGURE = "RECONFIGURE";
+ static const char* RELAY_FORW = "RELAY_FORWARD";
+ static const char* RELAY_REPL = "RELAY_REPLY";
+ static const char* RELEASE = "RELEASE";
+ static const char* RENEW = "RENEW";
+ static const char* REPLY = "REPLY";
+ static const char* REQUEST = "REQUEST";
+ static const char* SOLICIT = "SOLICIT";
+ static const char* DHCPV4_QUERY = "DHCPV4_QUERY";
+ static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE";
+ static const char* UNKNOWN = "UNKNOWN";
+
+ switch (type) {
+ case DHCPV6_ADVERTISE:
+ return (ADVERTISE);
+
+ case DHCPV6_CONFIRM:
+ return (CONFIRM);
+
+ case DHCPV6_DECLINE:
+ return (DECLINE);
+
+ case DHCPV6_INFORMATION_REQUEST:
+ return (INFORMATION_REQUEST);
+
+ case DHCPV6_LEASEQUERY:
+ return (LEASEQUERY);
+
+ case DHCPV6_LEASEQUERY_REPLY:
+ return (LEASEQUERY_REPLY);
+
+ case DHCPV6_REBIND:
+ return (REBIND);
+
+ case DHCPV6_RECONFIGURE:
+ return (RECONFIGURE);
+
+ case DHCPV6_RELAY_FORW:
+ return (RELAY_FORW);
+
+ case DHCPV6_RELAY_REPL:
+ return (RELAY_REPL);
+
+ case DHCPV6_RELEASE:
+ return (RELEASE);
+
+ case DHCPV6_RENEW:
+ return (RENEW);
+
+ case DHCPV6_REPLY:
+ return (REPLY);
+
+ case DHCPV6_REQUEST:
+ return (REQUEST);
+
+ case DHCPV6_SOLICIT:
+ return (SOLICIT);
+
+ case DHCPV6_DHCPV4_QUERY:
+ return (DHCPV4_QUERY);
+
+ case DHCPV6_DHCPV4_RESPONSE:
+ return (DHCPV4_RESPONSE);
+
+ default:
+ ;
+ }
+ return (UNKNOWN);
+}
+
+const char* Pkt6::getName() const {
+ return (getName(getType()));
+}
+
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+ // We use index rather than iterator, because we need that as a parameter
+ // passed to getNonCopiedRelayOption()
+ for (size_t i = 0; i < question->relay_info_.size(); ++i) {
+ RelayInfo info;
+ info.msg_type_ = DHCPV6_RELAY_REPL;
+ info.hop_count_ = question->relay_info_[i].hop_count_;
+ info.linkaddr_ = question->relay_info_[i].linkaddr_;
+ info.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+ // Is there an interface-id option in this nesting level?
+ // If there is, we need to echo it back
+ OptionPtr opt = question->getNonCopiedRelayOption(D6O_INTERFACE_ID, i);
+ // taken from question->RelayInfo_[i].options_
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ // Same for relay-source-port option
+ opt = question->getNonCopiedRelayOption(D6O_RELAY_SOURCE_PORT, i);
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ /// @todo: Implement support for ERO (Echo Request Option, RFC4994)
+
+ // Add this relay-forw info (client's message) to our relay-repl
+ // message (server's response)
+ relay_info_.push_back(info);
+ }
+}
+
+HWAddrPtr
+Pkt6::getMACFromSrcLinkLocalAddr() {
+ if (relay_info_.empty()) {
+ // This is a direct message, use source address
+ return (getMACFromIPv6(remote_addr_));
+ }
+
+ // This is a relayed message, get the peer-addr from the first relay-forw
+ return (getMACFromIPv6(relay_info_[relay_info_.size() - 1].peeraddr_));
+}
+
+HWAddrPtr
+Pkt6::getMACFromIPv6RelayOpt() {
+ HWAddrPtr mac;
+
+ // This is not a direct message
+ if (!relay_info_.empty()) {
+ // RFC6969 Section 6: Look for the client_linklayer_addr option on the
+ // relay agent closest to the client
+ OptionPtr opt = getAnyRelayOption(D6O_CLIENT_LINKLAYER_ADDR,
+ RELAY_GET_FIRST);
+ if (opt) {
+ const OptionBuffer data = opt->getData();
+ // This client link address option is supposed to be
+ // 2 bytes of link-layer type followed by link-layer address.
+ if (data.size() >= 3) {
+ // +2, -2 means to skip the initial 2 bytes which are
+ // hwaddress type
+ mac.reset(new HWAddr(&data[0] + 2, data.size() - 2,
+ opt->getUint16()));
+
+ mac->source_ = HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION;
+ }
+ }
+ }
+
+ return mac;
+}
+
+HWAddrPtr
+Pkt6::getMACFromDocsisModem() {
+ HWAddrPtr mac;
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<
+ OptionVendor>(getNonCopiedOption(D6O_VENDOR_OPTS));
+
+ // Check if this is indeed DOCSIS3 environment
+ if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ // If it is, try to get device-id option
+ OptionPtr device_id = vendor->getOption(DOCSIS3_V6_DEVICE_ID);
+ if (device_id) {
+ // If the option contains any data, use it as MAC address
+ if (!device_id->getData().empty()) {
+ mac.reset(new HWAddr(device_id->getData(), HTYPE_DOCSIS));
+ mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_MODEM;
+ }
+ }
+ }
+
+ return mac;
+}
+
+HWAddrPtr
+Pkt6::getMACFromDocsisCMTS() {
+ HWAddrPtr mac;
+
+ // If the message passed through a CMTS, there'll
+ // CMTS-specific options in it.
+ if (!relay_info_.empty()) {
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<
+ OptionVendor>(getAnyRelayOption(D6O_VENDOR_OPTS,
+ RELAY_SEARCH_FROM_CLIENT));
+
+ // Check if this is indeed DOCSIS3 environment
+ if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ // Try to get cable modem mac
+ OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC);
+
+ // If the option contains any data, use it as MAC address
+ if (cm_mac && !cm_mac->getData().empty()) {
+ mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS));
+ mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS;
+ }
+ }
+ }
+
+ return (mac);
+}
+
+HWAddrPtr
+Pkt6::getMACFromRemoteIdRelayOption() {
+ HWAddrPtr mac;
+
+ // If this is relayed message
+ if (!relay_info_.empty()) {
+ // Get remote-id option from a relay agent closest to the client
+ OptionPtr opt = getAnyRelayOption(D6O_REMOTE_ID, RELAY_GET_FIRST);
+ if (opt) {
+ const OptionBuffer data = opt->getData();
+ // This remote-id option is supposed to be 4 bytes of
+ // of enterprise-number followed by remote-id.
+ if (data.size() >= 5) {
+ // Let's get the interface this packet was received on.
+ // We need it to get the hardware type.
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_);
+ uint16_t hwtype = 0; // not specified
+
+ // If we get the interface HW type, great! If not,
+ // let's not panic.
+ if (iface) {
+ hwtype = iface->getHWType();
+ }
+
+ size_t len = data.size() - 4;
+
+ if (len > HWAddr::MAX_HWADDR_LEN) {
+ len = HWAddr::MAX_HWADDR_LEN;
+ }
+
+ // Skip the initial 4 bytes which are enterprise-number.
+ mac.reset(new HWAddr(&data[0] + 4, len, hwtype));
+ mac->source_ = HWAddr::HWADDR_SOURCE_REMOTE_ID;
+ }
+ }
+ }
+
+ return (mac);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc