Browse Source

[2827] Pkt6 now supports relays properly.

Tomek Mrugalski 12 years ago
parent
commit
859e0b3e82

+ 5 - 0
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

+ 8 - 2
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

+ 13 - 4
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.
     ///

+ 3 - 6
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

+ 42 - 46
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);

+ 53 - 15
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;

+ 9 - 3
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

+ 248 - 5
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)));
+}
 
 }