BIND 10 master, updated. 1eb1178479503a5daf0f0ba43bf729d64c6ecd7c [master] Merge branch 'trac2827' (relay support in dhcp/Pkt6)

BIND 10 source code commits bind10-changes at lists.isc.org
Thu Apr 4 15:56:32 UTC 2013


The branch, master has been updated
       via  1eb1178479503a5daf0f0ba43bf729d64c6ecd7c (commit)
       via  29c3f7f4e82d7e85f0f5fb692345fd55092796b4 (commit)
       via  492b1ef54094f220280d58b42ee0749ef829a045 (commit)
       via  7c0534d4b3dcd4b318dca8ebdcb1f1424c14931b (commit)
       via  859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 (commit)
       via  3511c6e6512c0004d9332ea85d1d3d4c03a414e0 (commit)
      from  d538f9ed878a0abf65a91b1ab70d86d58aaad9aa (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 1eb1178479503a5daf0f0ba43bf729d64c6ecd7c
Merge: d538f9e 29c3f7f
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Thu Apr 4 17:56:08 2013 +0200

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

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                    |    5 +
 src/lib/dhcp/libdhcp++.cc                    |   19 +-
 src/lib/dhcp/libdhcp++.h                     |   21 ++-
 src/lib/dhcp/option_custom.cc                |   11 +-
 src/lib/dhcp/pkt6.cc                         |  235 +++++++++++++++++++++++-
 src/lib/dhcp/pkt6.h                          |  106 ++++++++++-
 src/lib/dhcp/tests/option_custom_unittest.cc |   14 +-
 src/lib/dhcp/tests/pkt6_unittest.cc          |  247 +++++++++++++++++++++++++-
 8 files changed, 630 insertions(+), 28 deletions(-)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index f6ce7a3..ae7935d 100644
--- a/ChangeLog
+++ b/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
 
 598.	[func]*		jinmei
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 78f511d..697c33e 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/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
 // 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,
-                               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 length = buf.size();
 
@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
     while (offset + 4 <= length) {
         uint16_t opt_type = isc::util::readUint16(&buf[offset]);
         offset += 2;
+
         uint16_t opt_len = isc::util::readUint16(&buf[offset]);
         offset += 2;
 
@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             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
         // code is non-unique within this container however at this point we
         // 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,
-                                 isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::Option::OptionCollection& options) {
     size_t offset = 0;
 
     // Get the list of stdandard option definitions.
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c242611..9d8bcab 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/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
 // 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.
     ///
-    /// 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 options Reference to option container. Options will be
     ///        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,
-                                 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.
     ///
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 3d2a1a9..e807928 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/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
 // 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
                     // representation of this FQDN.
                     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
                     // which size can't be determined. Thus we consume the
                     // 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
                     // should have checked wheter it is a case for this option.
                     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
                     // truncated because there is no remaining data to initialize
                     // an option field.
-                    if (data_size == 0) {
-                        isc_throw(OutOfRange, "option buffer truncated");
-                    }
+                    isc_throw(OutOfRange, "option buffer truncated");
                 }
             } else {
                 // Our data field requires that there is a certain chunk of
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index c3a98bf..c97281e 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/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
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,17 @@
 #include <sstream>
 
 using namespace std;
+using namespace isc::asiolink;
 
 namespace isc {
 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 */) :
     proto_(proto),
     msg_type_(0),
@@ -54,9 +61,61 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
 }
 
 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
 
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (Option::OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -82,6 +141,50 @@ Pkt6::pack() {
 bool
 Pkt6::packUDP() {
     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)
         bufferOut_.writeUint8(msg_type_);
         // store 3-octet transaction-id
@@ -127,12 +230,43 @@ Pkt6::unpackUDP() {
         return (false);
     }
     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;
 
     try {
-        OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
+        OptionBuffer opt_buffer(begin, end);
 
         LibDHCP::unpackOptions6(opt_buffer, options_);
     } catch (const Exception& e) {
@@ -143,6 +277,97 @@ Pkt6::unpackUDP() {
 }
 
 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() {
     isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
               "not implemented yet.");
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index afb85d2..0bf4192 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/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
 // purpose with or without fee is hereby granted, provided that the above
@@ -32,15 +32,40 @@ namespace dhcp {
 
 class Pkt6 {
 public:
-    /// specifes DHCPv6 packet header length
+    /// specifies non-relayed DHCPv6 packet header length (over UDP)
     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
     enum DHCPv6Proto {
         UDP = 0, // most packets are UDP
         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
     ///
     /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
@@ -89,7 +114,6 @@ public:
     /// @return reference to output buffer
     const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
 
-
     /// @brief Returns reference to input buffer.
     ///
     /// @return reference to input buffer
@@ -160,6 +184,23 @@ public:
     /// @return pointer to found option (or NULL)
     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.
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -246,7 +287,7 @@ public:
     /// @brief Returns packet timestamp.
     ///
     /// Returns packet timestamp value updated when
-    /// packet is received or send.
+    /// packet is received or sent.
     ///
     /// @return packet timestamp.
     const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
@@ -259,8 +300,18 @@ public:
     /// @return interface name
     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
     ///
+    /// @todo: Text mentions protected, but this is really public
+    ///
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
@@ -305,6 +356,15 @@ public:
     ///         be freed by the caller.
     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:
     /// Builds on wire packet for TCP transmission.
     ///
@@ -340,6 +400,44 @@ protected:
     /// @return true, if build was successful
     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)
     DHCPv6Proto proto_;
 
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index e0ae727..9fb72e9 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/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
 // 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
     // 3 data fields for this option but the length of the data is insufficient
     // 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
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index cdaad3b..b5a745e 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/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
 // purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,15 @@
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp6.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 <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <util/encode/hex.h>
 #include <gtest/gtest.h>
 
 #include <iostream>
@@ -99,6 +105,67 @@ Pkt6* capture1() {
     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) {
     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)));
+}
 
 }



More information about the bind10-changes mailing list