Browse Source

[master] Merge branch 'trac2827' (relay support in dhcp/Pkt6)

Conflicts:
	ChangeLog
Tomek Mrugalski 12 years ago
parent
commit
1eb1178479

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+599.	[func]		tomek
+	libdhcp++: Pkt6 class is now able to parse and build relayed DHCPv6
+	messages.
+	(Trac #2827, git 29c3f7f4e82d7e85f0f5fb692345fd55092796b4)
+
 bind10-1.0.0beta1 released on April 4, 2013
 bind10-1.0.0beta1 released on April 4, 2013
 
 
 598.	[func]*		jinmei
 598.	[func]*		jinmei

+ 16 - 3
src/lib/dhcp/libdhcp++.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u,
 
 
 
 
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
-                               isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::Option::OptionCollection& options,
+                               size_t* relay_msg_offset /* = 0 */,
+                               size_t* relay_msg_len /* = 0 */) {
     size_t offset = 0;
     size_t offset = 0;
     size_t length = buf.size();
     size_t length = buf.size();
 
 
@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
     while (offset + 4 <= length) {
     while (offset + 4 <= length) {
         uint16_t opt_type = isc::util::readUint16(&buf[offset]);
         uint16_t opt_type = isc::util::readUint16(&buf[offset]);
         offset += 2;
         offset += 2;
+
         uint16_t opt_len = isc::util::readUint16(&buf[offset]);
         uint16_t opt_len = isc::util::readUint16(&buf[offset]);
         offset += 2;
         offset += 2;
 
 
@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             return (offset);
             return (offset);
         }
         }
 
 
+        if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+            // remember offset of the beginning of the relay-msg option
+            *relay_msg_offset = offset;
+            *relay_msg_len = opt_len;
+
+            // do not create that relay-msg option
+            offset += opt_len;
+            continue;
+        }
+
         // Get all definitions with the particular option code. Note that option
         // Get all definitions with the particular option code. Note that option
         // code is non-unique within this container however at this point we
         // code is non-unique within this container however at this point we
         // expect to get one option definition with the particular code. If more
         // expect to get one option definition with the particular code. If more
@@ -193,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 }
 }
 
 
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::Option::OptionCollection& options) {
     size_t offset = 0;
     size_t offset = 0;
 
 
     // Get the list of stdandard option definitions.
     // Get the list of stdandard option definitions.

+ 17 - 4
src/lib/dhcp/libdhcp++.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -115,14 +115,27 @@ public:
 
 
     /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
     /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
     ///
     ///
-    /// Parses provided buffer and stores created Option objects
-    /// in options container.
+    /// Parses provided buffer and stores created Option objects in options
+    /// container. The last two parameters are optional and are used in
+    /// relay parsing. If they are specified, relay-msg option is not created,
+    /// but rather those two parameters are specified to point out where
+    /// the relay-msg option resides and what is its length. This is perfromance
+    /// optimization that avoids unnecessary copying of potentially large
+    /// relay-msg option. It is not used for anything, except in the next
+    /// iteration its content will be treated as buffer to be parsed.
     ///
     ///
     /// @param buf Buffer to be parsed.
     /// @param buf Buffer to be parsed.
     /// @param options Reference to option container. Options will be
     /// @param options Reference to option container. Options will be
     ///        put here.
     ///        put here.
+    /// @param relay_msg_offset reference to a size_t structure. If specified,
+    ///        offset to beginning of relay_msg option will be stored in it.
+    /// @param relay_msg_len reference to a size_t structure. If specified,
+    ///        length of the relay_msg option will be stored in it.
+    /// @return offset to the first byte after last parsed option
     static size_t unpackOptions6(const OptionBuffer& buf,
     static size_t unpackOptions6(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options);
+                                 isc::dhcp::Option::OptionCollection& options,
+                                 size_t* relay_msg_offset = 0,
+                                 size_t* relay_msg_len = 0);
 
 
     /// Registers factory method that produces options of specific option types.
     /// Registers factory method that produces options of specific option types.
     ///
     ///

+ 4 - 7
src/lib/dhcp/option_custom.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -230,7 +230,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 1 byte larger than the size of the string
                     // 1 byte larger than the size of the string
                     // representation of this FQDN.
                     // representation of this FQDN.
                     data_size = fqdn.size() + 1;
                     data_size = fqdn.size() + 1;
-                } else {
+                } else if ( (*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE) ) {
                     // In other case we are dealing with string or binary value
                     // In other case we are dealing with string or binary value
                     // which size can't be determined. Thus we consume the
                     // which size can't be determined. Thus we consume the
                     // remaining part of the buffer for it. Note that variable
                     // remaining part of the buffer for it. Note that variable
@@ -238,14 +238,11 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // that the validate() function in OptionDefinition object
                     // that the validate() function in OptionDefinition object
                     // should have checked wheter it is a case for this option.
                     // should have checked wheter it is a case for this option.
                     data_size = std::distance(data, data_buf.end());
                     data_size = std::distance(data, data_buf.end());
-                }
-                if (data_size == 0) {
+                } else {
                     // If we reached the end of buffer we assume that this option is
                     // If we reached the end of buffer we assume that this option is
                     // truncated because there is no remaining data to initialize
                     // truncated because there is no remaining data to initialize
                     // an option field.
                     // an option field.
-                    if (data_size == 0) {
-                        isc_throw(OutOfRange, "option buffer truncated");
-                    }
+                    isc_throw(OutOfRange, "option buffer truncated");
                 }
                 }
             } else {
             } else {
                 // Our data field requires that there is a certain chunk of
                 // Our data field requires that there is a certain chunk of

+ 230 - 5
src/lib/dhcp/pkt6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,17 @@
 #include <sstream>
 #include <sstream>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::asiolink;
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
+Pkt6::RelayInfo::RelayInfo()
+    :msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_("::"), relay_msg_len_(0) {
+    // interface_id_, subscriber_id_, remote_id_ initialized to NULL
+    // echo_options_ initialized to empty collection
+}
+
 Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
 Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
     proto_(proto),
     proto_(proto),
     msg_type_(0),
     msg_type_(0),
@@ -54,9 +61,61 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
 }
 }
 
 
 uint16_t Pkt6::len() {
 uint16_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]));
+    }
+}
+
+OptionPtr Pkt6::getRelayOption(uint16_t opt_type, 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.");
+    }
+
+    for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
+         it != relay_info_[relay_level].options_.end(); ++it) {
+        if ((*it).second->getType() == opt_type) {
+            return (it->second);
+        }
+    }
+
+    return (OptionPtr());
+}
+
+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 (Option::OptionCollection::const_iterator opt = relay.options_.begin();
+         opt != relay.options_.end(); ++opt) {
+        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
     uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
 
 
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (Option::OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          it != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         length += (*it).second->len();
@@ -82,6 +141,50 @@ Pkt6::pack() {
 bool
 bool
 Pkt6::packUDP() {
 Pkt6::packUDP() {
     try {
     try {
+
+        // 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 RFC3315, section 7)
+                bufferOut_.writeUint8(relay->msg_type_);
+                bufferOut_.writeUint8(relay->hop_count_);
+                bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
+                                     isc::asiolink::V6ADDRESS_LEN);
+                bufferOut_.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 (Option::OptionCollection::const_iterator opt =
+                         relay->options_.begin();
+                     opt != relay->options_.end(); ++opt) {
+                    (opt->second)->pack(bufferOut_);
+                }
+
+                // 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)
+                bufferOut_.writeUint16(D6O_RELAY_MSG);
+                bufferOut_.writeUint16(relay->relay_msg_len_);
+            }
+
+        }
+
         // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
         // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
         bufferOut_.writeUint8(msg_type_);
         bufferOut_.writeUint8(msg_type_);
         // store 3-octet transaction-id
         // store 3-octet transaction-id
@@ -127,12 +230,43 @@ Pkt6::unpackUDP() {
         return (false);
         return (false);
     }
     }
     msg_type_ = data_[0];
     msg_type_ = data_[0];
-    transid_ = ( (data_[1]) << 16 ) +
-        ((data_[2]) << 8) + (data_[3]);
+    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:
+    default: // assume that uknown messages are not using relay format
+        {
+            return (unpackMsg(data_.begin(), data_.end()));
+        }
+    case DHCPV6_RELAY_FORW:
+    case DHCPV6_RELAY_REPL:
+        return (unpackRelayMsg());
+    }
+}
+
+bool
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+                OptionBuffer::const_iterator end) {
+    if (std::distance(begin, end) < 4) {
+        // truncated message (less than 4 bytes)
+        return (false);
+    }
+
+    msg_type_ = *begin++;
+
+    transid_ = ( (*begin++) << 16 ) +
+        ((*begin++) << 8) + (*begin++);
     transid_ = transid_ & 0xffffff;
     transid_ = transid_ & 0xffffff;
 
 
     try {
     try {
-        OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
+        OptionBuffer opt_buffer(begin, end);
 
 
         LibDHCP::unpackOptions6(opt_buffer, options_);
         LibDHCP::unpackOptions6(opt_buffer, options_);
     } catch (const Exception& e) {
     } catch (const Exception& e) {
@@ -143,6 +277,97 @@ Pkt6::unpackUDP() {
 }
 }
 
 
 bool
 bool
+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 offets 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)
+
+        try {
+            // parse the rest as options
+            OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
+            LibDHCP::unpackOptions6(opt_buffer, 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 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.");
+                return false;
+            }
+            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.
+        } catch (const Exception& e) {
+            /// @todo: throw exception here once we turn this function to void.
+            return (false);
+        }
+    }
+
+    if ( (offset == data_.size()) && (bufsize == 0) ) {
+        // message has been parsed completely
+        return (true);
+    }
+
+    /// @todo: log here that there are additional unparsed bytes
+    return (true);
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+    if (relay_info_.size() > 32) {
+        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);
+}
+
+bool
 Pkt6::unpackTCP() {
 Pkt6::unpackTCP() {
     isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
     isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
               "not implemented yet.");
               "not implemented yet.");

+ 102 - 4
src/lib/dhcp/pkt6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -32,15 +32,40 @@ namespace dhcp {
 
 
 class Pkt6 {
 class Pkt6 {
 public:
 public:
-    /// specifes DHCPv6 packet header length
+    /// specifies non-relayed DHCPv6 packet header length (over UDP)
     const static size_t DHCPV6_PKT_HDR_LEN = 4;
     const static size_t DHCPV6_PKT_HDR_LEN = 4;
 
 
+    /// specifies relay DHCPv6 packet header length (over UDP)
+    const static size_t DHCPV6_RELAY_HDR_LEN = 34;
+
     /// DHCPv6 transport protocol
     /// DHCPv6 transport protocol
     enum DHCPv6Proto {
     enum DHCPv6Proto {
         UDP = 0, // most packets are UDP
         UDP = 0, // most packets are UDP
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
     };
 
 
+
+    /// @brief structure that describes a single relay information
+    ///
+    /// Client sends messages. Each relay along its way will encapsulate the message.
+    /// This structure represents all information added by a single relay.
+    struct RelayInfo {
+
+        /// @brief default constructor
+        RelayInfo();
+        uint8_t   msg_type_;               ///< message type (RELAY-FORW oro RELAY-REPL)
+        uint8_t   hop_count_;              ///< number of traversed relays (up to 32)
+        isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
+        isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply
+
+        /// @brief length of the relay_msg_len
+        /// Used when calculating length during pack/unpack
+        uint16_t  relay_msg_len_;
+
+        /// options received from a specified relay, except relay-msg option
+        isc::dhcp::Option::OptionCollection options_;
+    };
+
     /// Constructor, used in replying to a message
     /// Constructor, used in replying to a message
     ///
     ///
     /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
     /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
@@ -89,7 +114,6 @@ public:
     /// @return reference to output buffer
     /// @return reference to output buffer
     const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
     const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
 
 
-
     /// @brief Returns reference to input buffer.
     /// @brief Returns reference to input buffer.
     ///
     ///
     /// @return reference to input buffer
     /// @return reference to input buffer
@@ -160,6 +184,23 @@ public:
     /// @return pointer to found option (or NULL)
     /// @return pointer to found option (or NULL)
     OptionPtr getOption(uint16_t type);
     OptionPtr getOption(uint16_t type);
 
 
+    /// @brief returns option inserted by relay
+    ///
+    /// Returns an option from specified relay scope (inserted by a given relay
+    /// if this is received packet or to be decapsulated by a given relay if
+    /// this is a transmitted packet). nesting_level specifies which relay
+    /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+    /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+    /// (relay closest to the client).
+    ///
+    /// @throw isc::OutOfRange if nesting level has invalid value.
+    ///
+    /// @param option_code code of the requested option
+    /// @param nesting_level see description above
+    ///
+    /// @return pointer to the option (or NULL if there is no such option)
+    OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
+
     /// @brief Returns all instances of specified type.
     /// @brief Returns all instances of specified type.
     ///
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -246,7 +287,7 @@ public:
     /// @brief Returns packet timestamp.
     /// @brief Returns packet timestamp.
     ///
     ///
     /// Returns packet timestamp value updated when
     /// Returns packet timestamp value updated when
-    /// packet is received or send.
+    /// packet is received or sent.
     ///
     ///
     /// @return packet timestamp.
     /// @return packet timestamp.
     const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
     const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
@@ -259,8 +300,18 @@ public:
     /// @return interface name
     /// @return interface name
     void setIface(const std::string& iface ) { iface_ = iface; };
     void setIface(const std::string& iface ) { iface_ = iface; };
 
 
+    /// @brief add information about one traversed relay
+    ///
+    /// This adds information about one traversed relay, i.e.
+    /// one relay-forw or relay-repl level of encapsulation.
+    ///
+    /// @param relay structure with necessary relay information
+    void addRelayInfo(const RelayInfo& relay);
+
     /// collection of options present in this message
     /// collection of options present in this message
     ///
     ///
+    /// @todo: Text mentions protected, but this is really public
+    ///
     /// @warning This protected member is accessed by derived
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
     /// classes directly. One of such derived classes is
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
@@ -305,6 +356,15 @@ public:
     ///         be freed by the caller.
     ///         be freed by the caller.
     const char* getName() const;
     const char* getName() const;
 
 
+    /// relay information
+    ///
+    /// this is a public field. Otherwise we hit one of the two problems:
+    /// we return reference to an internal field (and that reference could
+    /// be potentially used past Pkt6 object lifetime causing badness) or
+    /// we return a copy (which is inefficient and also causes any updates
+    /// to be impossible). Therefore public field is considered the best
+    /// (or least bad) solution.
+    std::vector<RelayInfo> relay_info_;
 protected:
 protected:
     /// Builds on wire packet for TCP transmission.
     /// Builds on wire packet for TCP transmission.
     ///
     ///
@@ -340,6 +400,44 @@ protected:
     /// @return true, if build was successful
     /// @return true, if build was successful
     bool unpackUDP();
     bool unpackUDP();
 
 
+    /// @brief unpacks direct (non-relayed) message
+    ///
+    /// This method unpacks specified buffer range as a direct
+    /// (e.g. solicit or request) message. This method is called from
+    /// unpackUDP() when received message is detected to be direct.
+    ///
+    /// @param begin start of the buffer
+    /// @param end end of the buffer
+    /// @return true if parsing was successful and there are no leftover bytes
+    bool unpackMsg(OptionBuffer::const_iterator begin,
+                   OptionBuffer::const_iterator end);
+
+    /// @brief unpacks relayed message (RELAY-FORW or RELAY-REPL)
+    ///
+    /// This method is called from unpackUDP() when received message
+    /// is detected to be relay-message. It goes iteratively over
+    /// all relays (if there are multiple encapsulation levels).
+    ///
+    /// @return true if parsing was successful
+    bool unpackRelayMsg();
+
+    /// @brief calculates overhead introduced in specified relay
+    ///
+    /// It is used when calculating message size and packing message
+    /// @param relay RelayInfo structure that holds information about relay
+    /// @return number of bytes needed to store relay information
+    uint16_t getRelayOverhead(const RelayInfo& relay) const;
+
+    /// @brief calculates overhead for all relays defined for this message
+    /// @return number of bytes needed to store all relay information
+    uint16_t calculateRelaySizes();
+
+    /// @brief calculates size of the message as if it was not relayed at all
+    ///
+    /// This is equal to len() if the message was not relayed.
+    /// @return number of bytes required to store the message
+    uint16_t directLen() const;
+
     /// UDP (usually) or TCP (bulk leasequery or failover)
     /// UDP (usually) or TCP (bulk leasequery or failover)
     DHCPv6Proto proto_;
     DHCPv6Proto proto_;
 
 

+ 10 - 4
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -766,9 +766,15 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
     // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
     // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
     // 3 data fields for this option but the length of the data is insufficient
     // 3 data fields for this option but the length of the data is insufficient
     // to initialize 3 data field.
     // to initialize 3 data field.
-    EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
-        isc::OutOfRange
+
+    // @todo:
+    // Currently the code was modified to allow empty string or empty binary data
+    // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we
+    // decide how to treat zero length strings and binary data (they are typically
+    // valid or invalid on a per option basis, so there likely won't be a single
+    // one answer to all)
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18))
     );
     );
 
 
     // Try to further reduce the length of the buffer to make it insufficient
     // Try to further reduce the length of the buffer to make it insufficient

+ 246 - 1
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,15 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <util/encode/hex.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <iostream>
 #include <iostream>
@@ -99,6 +105,67 @@ Pkt6* capture1() {
     return (pkt);
     return (pkt);
 }
 }
 
 
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark. It includes a SOLICIT
+/// message that passed through two relays. Each relay include interface-id,
+/// remote-id and relay-forw encapsulation. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+///  - relay message option
+///      - RELAY-FORW
+///          - interface-id option
+///          - remote-id option
+///          - RELAY-FORW
+///              SOLICIT
+///                  - client-id option
+///                  - ia_na option
+///                  - elapsed time
+///                  - ORO
+///  - interface-id option
+///  - remote-id option
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+Pkt6* capture2() {
+
+    // string exported from Wireshark
+    string hex_string =
+        "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a900"
+        "09007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c"
+        "18a9001200154953414d3134342065746820312f312f30352f30310025000400000de9"
+        "00090036016b4fe20001000e0001000118b033410000215c18a90003000c00000001ff"
+        "ffffffffffffff00080002000000060006001700f200f30012001c4953414d3134347c"
+        "3239397c697076367c6e743a76703a313a313130002500120000197f0001000118b033"
+        "410000215c18a9";
+
+    std::vector<uint8_t> bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(hex_string, bin);
+
+    Pkt6* pkt = new Pkt6(&bin[0], bin.size());
+    pkt->setRemotePort(547);
+    pkt->setRemoteAddr(IOAddress("fe80::1234"));
+    pkt->setLocalPort(547);
+    pkt->setLocalAddr(IOAddress("ff05::1:3"));
+    pkt->setIndex(2);
+    pkt->setIface("eth0");
+    return (pkt);
+}
 
 
 TEST_F(Pkt6Test, unpack_solicit1) {
 TEST_F(Pkt6Test, unpack_solicit1) {
     Pkt6* sol = capture1();
     Pkt6* sol = capture1();
@@ -306,5 +373,183 @@ TEST_F(Pkt6Test, getName) {
     }
     }
 }
 }
 
 
+// This test verifies that a fancy solicit that passed through two
+// relays can be parsed properly. See capture2() method description
+// for details regarding the packet.
+TEST_F(Pkt6Test, relayUnpack) {
+    boost::scoped_ptr<Pkt6> msg(capture2());
+
+    EXPECT_NO_THROW(msg->unpack());
+
+    EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+    EXPECT_EQ(217, msg->len());
+
+    ASSERT_EQ(2, msg->relay_info_.size());
+
+    OptionPtr opt;
+
+    // part 1: Check options inserted by the first relay
+
+    // There should be 2 options in first relay
+    EXPECT_EQ(2, msg->relay_info_[0].options_.size());
+
+    // There should be interface-id option
+    ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0));
+    OptionBuffer data = opt->getData();
+    EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header
+    EXPECT_EQ(data.size(), 28);
+    // That's a strange interface-id, but this is a real life example
+    EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));
+
+    // get the remote-id option
+    ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
+    EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
+    boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+    uint32_t vendor_id = custom->readInteger<uint32_t>(0);
+    EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks
+
+    uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+                                     0x00, 0x21, 0x5c, 0x18, 0xa9 };
+    OptionBuffer remote_id = custom->readBinary(1);
+    ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
+    ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
+
+    // part 2: Check options inserted by the second relay
+
+    // get the interface-id from the second relay
+    ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
+    data = opt->getData();
+    EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
+    EXPECT_EQ(data.size(), 21);
+    EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));
+
+    // get the remote-id option
+    ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
+    EXPECT_EQ(8, opt->len());
+    custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+    vendor_id = custom->readInteger<uint32_t>(0);
+    EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum
+    // @todo: See if we can validate empty remote-id field
+
+    // Let's check if there is no leak between options stored in
+    // the SOLICIT message and the relay.
+    EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1));
+
+
+    // Part 3: Let's check options in the message itself
+    // This is not redundant compared to other direct messages tests,
+    // as we parsed it differently
+    EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+    EXPECT_EQ(0x6b4fe2, msg->getTransid());
+
+    ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
+    EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
+    uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+                                     0x00, 0x21, 0x5c, 0x18, 0xa9 };
+    data = opt->getData();
+    ASSERT_EQ(data.size(), sizeof(expected_client_id));
+    ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));
+
+    ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA));
+    boost::shared_ptr<Option6IA> ia =
+        boost::dynamic_pointer_cast<Option6IA>(opt);
+    ASSERT_TRUE(ia);
+    EXPECT_EQ(1, ia->getIAID());
+    EXPECT_EQ(0xffffffff, ia->getT1());
+    EXPECT_EQ(0xffffffff, ia->getT2());
+
+    ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME));
+    EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header
+    boost::shared_ptr<OptionInt<uint16_t> > elapsed =
+        boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
+    ASSERT_TRUE(elapsed);
+    EXPECT_EQ(0, elapsed->getValue());
+
+    ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
+    boost::shared_ptr<OptionIntArray<uint16_t> > oro =
+        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt);
+    const std::vector<uint16_t> oro_list = oro->getValues();
+    EXPECT_EQ(3, oro_list.size());
+    EXPECT_EQ(23, oro_list[0]);
+    EXPECT_EQ(242, oro_list[1]);
+    EXPECT_EQ(243, oro_list[2]);
+}
+
+// This test verified that message with relay information can be
+// packed and then unpacked.
+TEST_F(Pkt6Test, relayPack) {
+
+    boost::scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+
+    Pkt6::RelayInfo relay1;
+    relay1.msg_type_ = DHCPV6_RELAY_REPL;
+    relay1.hop_count_ = 17; // not very miningful, but useful for testing
+    relay1.linkaddr_ = IOAddress("2001:db8::1");
+    relay1.peeraddr_ = IOAddress("fe80::abcd");
+
+    uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
+    vector<uint8_t> relay_data(relay_opt_data, relay_opt_data + sizeof(relay_opt_data));
+
+    OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
+
+    relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(optRelay1->getType(), optRelay1));
+
+    OptionPtr opt1(new Option(Option::V6, 100));
+    OptionPtr opt2(new Option(Option::V6, 101));
+    OptionPtr opt3(new Option(Option::V6, 102));
+    // let's not use zero-length option type 3 as it is IA_NA
+
+    parent->addRelayInfo(relay1);
+
+    parent->addOption(opt1);
+    parent->addOption(opt2);
+    parent->addOption(opt3);
+
+    EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());
+
+    EXPECT_TRUE(parent->pack());
+
+    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
+              + Pkt6::DHCPV6_RELAY_HDR_LEN // relay header
+              + Option::OPTION6_HDR_LEN // relay-msg
+              + optRelay1->len(),
+              parent->len());
+
+    // create second packet,based on assembled data from the first one
+    boost::scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
+                                           parent->getBuffer().getData()),
+                                           parent->getBuffer().getLength()));
+
+    // now recreate options list
+    EXPECT_TRUE( clone->unpack() );
+
+    // transid, message-type should be the same as before
+    EXPECT_EQ(parent->getTransid(), parent->getTransid());
+    EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType());
+
+    EXPECT_TRUE( clone->getOption(100));
+    EXPECT_TRUE( clone->getOption(101));
+    EXPECT_TRUE( clone->getOption(102));
+    EXPECT_FALSE(clone->getOption(103));
+
+    // Now check relay info
+    ASSERT_EQ(1, clone->relay_info_.size());
+    EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_);
+    EXPECT_EQ(17, clone->relay_info_[0].hop_count_);
+    EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText());
+    EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText());
+
+    // There should be exactly one option
+    EXPECT_EQ(1, clone->relay_info_[0].options_.size());
+    OptionPtr opt = clone->getRelayOption(200, 0);
+    EXPECT_TRUE(opt);
+    EXPECT_EQ(opt->getType() , optRelay1->getType());
+    EXPECT_EQ(opt->len(), optRelay1->len());
+    OptionBuffer data = opt->getData();
+    ASSERT_EQ(data.size(), sizeof(relay_opt_data));
+    EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
+}
 
 
 }
 }