BIND 10 trac2827, updated. 859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 [2827] Pkt6 now supports relays properly.

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Mar 6 15:21:47 UTC 2013


The branch, trac2827 has been updated
       via  859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 (commit)
      from  3511c6e6512c0004d9332ea85d1d3d4c03a414e0 (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 859e0b3e82d4cc5270d8fb557f0120dc35edd1a3
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Wed Mar 6 16:21:25 2013 +0100

    [2827] Pkt6 now supports relays properly.

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

Summary of changes:
 ChangeLog                                    |    5 +
 src/lib/dhcp/libdhcp++.cc                    |   10 +-
 src/lib/dhcp/libdhcp++.h                     |   17 +-
 src/lib/dhcp/option_custom.cc                |    9 +-
 src/lib/dhcp/pkt6.cc                         |   88 +++++----
 src/lib/dhcp/pkt6.h                          |   68 +++++--
 src/lib/dhcp/tests/option_custom_unittest.cc |   12 +-
 src/lib/dhcp/tests/pkt6_unittest.cc          |  253 +++++++++++++++++++++++++-
 8 files changed, 381 insertions(+), 81 deletions(-)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index d7af227..f6e4fda 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+5XX.	[func]		tomek
+	b10-dhcp6: Pkt6 class is now able to parse and build relayed
+	DHCPv6 messages.
+	(Trac #2827, git TBD)
+
 582.	[func]		naokikambe
 	New statistics items related unixdomain sockets added into Xfrout :
 	open, openfail, close, bindfail, acceptfail, accept, senderr, and
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 414257a..2f748aa 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -129,7 +129,8 @@ LibDHCP::optionFactory(Option::Universe u,
 
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
                                isc::dhcp::Option::OptionCollection& options,
-                               size_t* relay_msg_offset /* = 0 */) {
+                               size_t* relay_msg_offset /* = 0 */,
+                               size_t* relay_msg_len /* = 0 */) {
     size_t offset = 0;
     size_t length = buf.size();
 
@@ -153,9 +154,14 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             return (offset);
         }
 
-        if (opt_type == D6O_RELAY_MSG && relay_msg_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
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index 4b9c31d..cc8ae9d 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -115,18 +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 store here.
+    ///        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,
-                                 size_t* relay_msg_offset = 0);
+                                 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..f7187ca 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -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 d0306f5..265cc28 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -27,7 +27,7 @@ namespace isc {
 namespace dhcp {
 
 Pkt6::RelayInfo::RelayInfo()
-    :msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_(""), relay_msg_len_(0) {
+    :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
 }
@@ -69,22 +69,28 @@ uint16_t Pkt6::len() {
     }
 }
 
+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) {
     uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
         + Option::OPTION6_HDR_LEN; // header of the relay-msg option
 
-    if (relay.interface_id_) {
-        len += relay.interface_id_->len();
-    }
-    if (relay.subscriber_id_) {
-        len += relay.subscriber_id_->len();
-    }
-    if (relay.remote_id_) {
-        len += relay.remote_id_->len();
-    }
-
-    for (Option::OptionCollection::const_iterator opt = relay.echo_options_.begin();
-         opt != relay.echo_options_.end(); ++opt) {
+    for (Option::OptionCollection::const_iterator opt = relay.options_.begin();
+         opt != relay.options_.end(); ++opt) {
         len += (opt->second)->len();
     }
 
@@ -148,19 +154,9 @@ Pkt6::packUDP() {
                 bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
                                      isc::asiolink::V6ADDRESS_LEN);
 
-                if (relay->interface_id_) {
-                    relay->interface_id_->pack(bufferOut_);
-                }
-                if (relay->subscriber_id_) {
-                    relay->subscriber_id_->pack(bufferOut_);
-                }
-                if (relay->remote_id_) {
-                    relay->remote_id_->pack(bufferOut_);
-                }
-
                 for (Option::OptionCollection::const_iterator opt =
-                         relay->echo_options_.begin();
-                     opt != relay->echo_options_.end(); ++opt) {
+                         relay->options_.begin();
+                     opt != relay->options_.end(); ++opt) {
                     (opt->second)->pack(bufferOut_);
                 }
 
@@ -271,12 +267,13 @@ Pkt6::unpackRelayMsg() {
     size_t bufsize = data_.size();
     size_t offset = 0;
 
-    size_t relay_msg_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++];
@@ -287,46 +284,45 @@ Pkt6::unpackRelayMsg() {
         bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
 
         try {
-            Option::OptionCollection options;
-
             // parse the rest as options
             OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
-            LibDHCP::unpackOptions6(opt_buffer, options, &relay_msg_offset);
+            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);
 
-            Option::OptionCollection::const_iterator relay_iter = options.find(D6O_RELAY_MSG);
-            if (relay_iter == options.end()) {
-                // there's nothing to decapsulate. We give up.
-                isc_throw(InvalidOperation, "Mandatory relay_msg missing");
+            if (relay_msg_offset == 0 || relay_msg_len == 0) {
+                isc_throw(BadValue, "Mandatory relay-msg option missing");
             }
-            OptionPtr relay_msg = relay_iter->second;
 
             // store relay information parsed so far
             addRelayInfo(relay);
 
             /// @todo: implement ERO here
 
-            size_t inner_len = relay_msg->len() - relay_msg->getHeaderLen();
-            if (inner_len >= bufsize) {
+            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 = relay_msg->getUint8();
-            offset += relay_msg_offset;
-            bufsize = inner_len;
-
-            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 + inner_len));
+            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
-
+            // 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);
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index b8184a8..88a0acc 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -44,21 +44,26 @@ public:
         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_;
 
-        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
-        OptionPtr interface_id_;  ///< interface-id option (optional)
-        OptionPtr subscriber_id_; ///< subscriber-id (RFC4580)
-        OptionPtr remote_id_;     ///< remote-id (RFC4649)
-        uint16_t  relay_msg_len_; ///< length of the relay_msg_len
-
-        /// used for ERO (Echo Request Option, RFC 4994)
-        isc::dhcp::Option::OptionCollection echo_options_;
+        /// options received from a specified relay, except relay-msg option
+        isc::dhcp::Option::OptionCollection options_;
     };
 
     /// Constructor, used in replying to a message
@@ -109,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
@@ -180,6 +184,8 @@ public:
     /// @return pointer to found option (or NULL)
     OptionPtr getOption(uint16_t type);
 
+    OptionPtr getRelayOption(uint16_t type, uint8_t nesting_level);
+
     /// @brief Returns all instances of specified type.
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -335,6 +341,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.
     ///
@@ -370,15 +385,41 @@ 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
+    /// @return number of bytes needed to store relay information
     uint16_t getRelayOverhead(const RelayInfo& relay);
 
+    /// @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 were sent directly
+    ///
+    /// This is equal to len() if the message is direct.
+    /// @return number of bytes required to store the message
     uint16_t directLen();
 
     /// UDP (usually) or TCP (bulk leasequery or failover)
@@ -435,9 +476,6 @@ protected:
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
-
-    /// relay information
-    std::vector<RelayInfo> relay_info_;
 }; // Pkt6 class
 
 typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index a34852b..07e904d 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -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..db091c4 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -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>
@@ -31,6 +37,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace boost;
 
 namespace {
 // empty class for now, but may be extended once Addr6 becomes bigger
@@ -99,6 +106,67 @@ Pkt6* capture1() {
     return (pkt);
 }
 
+/// @brief creates doubly related 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();
@@ -234,7 +302,7 @@ TEST_F(Pkt6Test, addGetDelOptions) {
 }
 
 TEST_F(Pkt6Test, Timestamp) {
-    boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+    scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
 
     // Just after construction timestamp is invalid
     ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
@@ -243,17 +311,17 @@ TEST_F(Pkt6Test, Timestamp) {
     pkt->updateTimestamp();
 
     // Get updated packet time.
-    boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+    posix_time::ptime ts_packet = pkt->getTimestamp();
 
     // After timestamp is updated it should be date-time.
     ASSERT_FALSE(ts_packet.is_not_a_date_time());
 
     // Check current time.
-    boost::posix_time::ptime ts_now =
-        boost::posix_time::microsec_clock::universal_time();
+    posix_time::ptime ts_now =
+        posix_time::microsec_clock::universal_time();
 
     // Calculate period between packet time and now.
-    boost::posix_time::time_period ts_period(ts_packet, ts_now);
+    posix_time::time_period ts_period(ts_packet, ts_now);
 
     // Duration should be positive or zero.
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
@@ -306,5 +374,180 @@ 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) {
+    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
+    shared_ptr<OptionCustom> custom = 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 = 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));
+    shared_ptr<Option6IA> ia = 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
+    shared_ptr<OptionInt<uint16_t> > elapsed = dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
+    ASSERT_TRUE(elapsed);
+    EXPECT_EQ(0, elapsed->getValue());
+
+    ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
+    shared_ptr<OptionIntArray<uint16_t> > oro = 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) {
+
+    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, 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
+    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