Browse Source

[master] Merge branch 'trac4497_rebase'

Marcin Siodelski 9 years ago
parent
commit
e50d2ebe99
72 changed files with 2428 additions and 281 deletions
  1. 48 0
      src/bin/dhcp4/dhcp4_hooks.dox
  2. 22 0
      src/bin/dhcp4/dhcp4_srv.cc
  3. 3 0
      src/bin/dhcp4/dhcp4to6_ipc.cc
  4. 45 4
      src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc
  5. 80 0
      src/bin/dhcp4/tests/hooks_unittest.cc
  6. 51 0
      src/bin/dhcp6/dhcp6_hooks.dox
  7. 24 0
      src/bin/dhcp6/dhcp6_srv.cc
  8. 3 0
      src/bin/dhcp6/dhcp6to4_ipc.cc
  9. 15 0
      src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc
  10. 214 0
      src/bin/dhcp6/tests/hooks_unittest.cc
  11. 1 1
      src/bin/lfc/.gitignore
  12. 1 1
      src/bin/perfdhcp/.gitignore
  13. 2 2
      src/bin/perfdhcp/localized_option.h
  14. 1 0
      src/lib/cfgrpt/tests/.gitignore
  15. 55 17
      src/lib/dhcp/option.cc
  16. 70 20
      src/lib/dhcp/option.h
  17. 9 4
      src/lib/dhcp/option4_addrlst.cc
  18. 7 4
      src/lib/dhcp/option4_addrlst.h
  19. 15 4
      src/lib/dhcp/option4_client_fqdn.cc
  20. 15 5
      src/lib/dhcp/option4_client_fqdn.h
  21. 9 4
      src/lib/dhcp/option6_addrlst.cc
  22. 6 4
      src/lib/dhcp/option6_addrlst.h
  23. 10 4
      src/lib/dhcp/option6_client_fqdn.cc
  24. 8 5
      src/lib/dhcp/option6_client_fqdn.h
  25. 10 5
      src/lib/dhcp/option6_ia.cc
  26. 8 6
      src/lib/dhcp/option6_ia.h
  27. 10 5
      src/lib/dhcp/option6_iaaddr.cc
  28. 7 4
      src/lib/dhcp/option6_iaaddr.h
  29. 10 5
      src/lib/dhcp/option6_iaprefix.cc
  30. 8 5
      src/lib/dhcp/option6_iaprefix.h
  31. 9 4
      src/lib/dhcp/option6_status_code.cc
  32. 7 4
      src/lib/dhcp/option6_status_code.h
  33. 10 5
      src/lib/dhcp/option_custom.cc
  34. 7 4
      src/lib/dhcp/option_custom.h
  35. 14 5
      src/lib/dhcp/option_int.h
  36. 15 5
      src/lib/dhcp/option_int_array.h
  37. 9 4
      src/lib/dhcp/option_opaque_data_tuples.cc
  38. 9 7
      src/lib/dhcp/option_opaque_data_tuples.h
  39. 10 5
      src/lib/dhcp/option_string.cc
  40. 8 5
      src/lib/dhcp/option_string.h
  41. 10 6
      src/lib/dhcp/option_vendor.cc
  42. 8 5
      src/lib/dhcp/option_vendor.h
  43. 12 7
      src/lib/dhcp/option_vendor_class.cc
  44. 7 4
      src/lib/dhcp/option_vendor_class.h
  45. 20 5
      src/lib/dhcp/pkt.cc
  46. 128 10
      src/lib/dhcp/pkt.h
  47. 4 4
      src/lib/dhcp/pkt4.cc
  48. 9 1
      src/lib/dhcp/pkt4o6.cc
  49. 12 1
      src/lib/dhcp/pkt4o6.h
  50. 110 29
      src/lib/dhcp/pkt6.cc
  51. 85 6
      src/lib/dhcp/pkt6.h
  52. 1 0
      src/lib/dhcp/tests/Makefile.am
  53. 1 0
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  54. 790 0
      src/lib/dhcp/tests/option_copy_unittest.cc
  55. 7 1
      src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc
  56. 55 0
      src/lib/dhcp/tests/pkt4_unittest.cc
  57. 25 0
      src/lib/dhcp/tests/pkt4o6_unittest.cc
  58. 194 5
      src/lib/dhcp/tests/pkt6_unittest.cc
  59. 18 0
      src/lib/dhcpsrv/alloc_engine.cc
  60. 1 0
      src/lib/dhcpsrv/parsers/.gitignore
  61. 22 0
      src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
  62. 1 0
      src/lib/dns/rdata/generic/detail/.gitignore
  63. 1 1
      src/lib/eval/.gitignore
  64. 1 1
      src/lib/eval/evaluate.cc
  65. 2 2
      src/lib/eval/evaluate.h
  66. 1 0
      src/lib/eval/tests/.gitignore
  67. 17 17
      src/lib/eval/token.cc
  68. 17 17
      src/lib/eval/token.h
  69. 1 1
      src/lib/testutils/.gitignore
  70. 1 0
      src/lib/util/encode/.gitignore
  71. 1 0
      src/lib/util/random/.gitignore
  72. 1 1
      src/lib/util/tests/.gitignore

+ 48 - 0
src/bin/dhcp4/dhcp4_hooks.dox

@@ -291,4 +291,52 @@ packet processing. Hook points that are not specific to packet processing
   expired leases will remain in the database and their recovery will
   be attempted during the next reclaim cycle.
 
+@section dhcpv4HooksOptionsAccess Accessing DHCPv4 Options within a Packet
+When the server constructs a response message to a client it includes
+DHCP options configured for this client in a response message. Apart
+from the dynamically created options, such as Client FQDN option, it
+typically includes many options specified in the server configuration
+and held within the configuration structures by @c CfgMgr. Option
+instances are created once, during server configuration, and the
+@c CfgMgr holds pointers to those instances until the next server
+reconfiguration.
+
+When the server includes an option in a response message it copies
+a pointer to the instance of this option, rather than entire option.
+This ensures the good performance of response message creation. However,
+it also implies that any modification to the option carried in the
+DHCP response will affect an instance of this option in the server
+configuration structures. This is obviously not desired as it would
+affect all subsequent DHCP transactions involving this option. The
+DHCP server code avoids modifying the options included in the messages
+so it is possible to ensure good performance without a risk of
+accidentally modifying server configuration. The situation is
+different with hooks libraries which purpose is, in many cases,
+to modify values of options inserted by the server.
+
+Thus, @c Pkt class provides a mechanism to return a copy of an
+option to a caller (e.g. a callout), rather than an instance
+shared with the @c CfgMgr. This mechanism is enabled for all instances
+of @c Pkt4 passed to the callouts, i.e. "query4" and "response4"
+arguments. It is also automatically disabled when the callout
+returns the control back to the server.
+
+At every hook point, where the server passes an instance of a packet
+to the callouts, the server calls
+@c isc::dhcp::Pkt4::setCopyRetrievedOptions (true)
+to force copying options retrieved by @c isc::dhcp::Pkt4::getOption
+within callouts. The copied option replaces an original option within a
+packet and any modification to the option content by the callout
+would only affect the option instance associated with the packet.
+
+On the other hand, copying each retrieved option may be expensive.
+If performance of a hook library is a concern, it is possible for the
+hook library to disable copying retrieved options by calling
+@c isc::dhcp::Pkt4::setCopyRetrievedOptions (false) within a callout.
+In this case however, the hook library implementer must be aware that any
+modification of the option instance would affect the server configuration
+and may disrupt server's operation. Thus, disabling copying of retrieved
+options is not recommended unless the hook library is not intended
+to modify configured options carried within a packet.
+
 */

+ 22 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -467,6 +467,9 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
         // We're reusing callout_handle from previous calls
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
         // Set new arguments
         callout_handle->setArgument("query4", query);
         callout_handle->setArgument("subnet4", subnet);
@@ -735,6 +738,9 @@ Dhcpv4Srv::run_one() {
             // Delete previously set arguments
             callout_handle->deleteAllArguments();
 
+            // Enable copying options from the packet within hook library.
+            ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
+
             // Pass incoming packet as argument
             callout_handle->setArgument("response4", rsp);
 
@@ -804,6 +810,9 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
         // Pass incoming packet as argument
         callout_handle->setArgument("query4", query);
 
@@ -894,6 +903,9 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
+
         // Pass incoming packet as argument
         callout_handle->setArgument("query4", query);
 
@@ -983,6 +995,10 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         // Clear skip flag if it was set in previous callouts
         callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
 
+        // Enable copying options from the query and response packets within
+        // hook library.
+        ScopedEnableOptionsCopy<Pkt4> query_resp_options_copy(query, rsp);
+
         // Set our response
         callout_handle->setArgument("response4", rsp);
 
@@ -2064,6 +2080,9 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
             // Delete all previous arguments
             callout_handle->deleteAllArguments();
 
+            // Enable copying options from the packet within hook library.
+            ScopedEnableOptionsCopy<Pkt4> query4_options_copy(release);
+
             // Pass the original packet
             callout_handle->setArgument("query4", release);
 
@@ -2207,6 +2226,9 @@ Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline) {
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(decline);
+
         // Pass incoming Decline and the lease to be declined.
         callout_handle->setArgument("lease4", lease);
         callout_handle->setArgument("query4", decline);

+ 3 - 0
src/bin/dhcp4/dhcp4to6_ipc.cc

@@ -121,6 +121,9 @@ void Dhcp4to6Ipc::handler() {
             // Delete previously set arguments
             callout_handle->deleteAllArguments();
 
+            // Enable copying options from the packet within hook library.
+            ScopedEnableOptionsCopy<Pkt4> response4_options_copy(rsp);
+
             // Pass incoming packet as argument
             callout_handle->setArgument("response4", rsp);
 

+ 45 - 4
src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc

@@ -21,6 +21,7 @@
 
 #include <gtest/gtest.h>
 #include <stdint.h>
+#include <utility>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -64,6 +65,10 @@ public:
         EXPECT_TRUE(srv);
         // Let's wipe all existing statistics.
         StatsMgr::instance().removeAll();
+
+        // Set the flags to false as we expect them to be set in callouts.
+        callback_recv_pkt_options_copy_ = std::make_pair(false, false);
+        callback_sent_pkt_options_copy_ = std::make_pair(false, false);
     }
 
     /// @brief Configure DHCP4o6 port.
@@ -85,9 +90,17 @@ public:
     ///
     /// @param callout_handle handle passed by the hooks framework
     /// @return always 0
-    static int
-        buffer4_receive_callout(CalloutHandle& callout_handle) {
+    static int buffer4_receive_callout(CalloutHandle& callout_handle) {
         callout_handle.getArgument("query4", callback_recv_pkt_);
+        Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_recv_pkt_);
+        if (pkt4) {
+            callback_recv_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
+            Pkt6Ptr pkt6 = pkt4->getPkt6();
+            if (pkt6) {
+                callback_recv_pkt_options_copy_.second =
+                    pkt6->isCopyRetrievedOptions();
+            }
+        }
         return (0);
     }
 
@@ -97,9 +110,17 @@ public:
     ///
     /// @param callout_handle handle passed by the hooks framework
     /// @return always 0
-    static int
-        buffer4_send_callout(CalloutHandle& callout_handle) {
+    static int buffer4_send_callout(CalloutHandle& callout_handle) {
         callout_handle.getArgument("response4", callback_sent_pkt_);
+        Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_sent_pkt_);
+        if (pkt4) {
+            callback_sent_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
+            Pkt6Ptr pkt6 = pkt4->getPkt6();
+            if (pkt6) {
+                callback_sent_pkt_options_copy_.second =
+                    pkt6->isCopyRetrievedOptions();
+            }
+        }
         return (0);
     }
 
@@ -109,6 +130,14 @@ public:
     /// @brief Response Pkt4 shared pointer returned in the send callout
     static Pkt4Ptr callback_sent_pkt_;
 
+    /// Flags indicating if copying retrieved options was enabled for
+    /// a received packet during callout execution.
+    static std::pair<bool, bool> callback_recv_pkt_options_copy_;
+
+    /// Flags indicating if copying retrieved options was enabled for
+    /// a sent packet during callout execution.
+    static std::pair<bool, bool> callback_sent_pkt_options_copy_;
+
     /// @brief reference to a controlled server
     ///
     /// Dhcp4to6Ipc::handler() uses the instance of the controlled server
@@ -124,6 +153,8 @@ private:
 
 Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_;
 Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_;
+std::pair<bool, bool> Dhcp4to6IpcTest::callback_recv_pkt_options_copy_;
+std::pair<bool, bool> Dhcp4to6IpcTest::callback_sent_pkt_options_copy_;
 
 void
 Dhcp4to6IpcTest::configurePort(uint16_t port) {
@@ -192,6 +223,11 @@ TEST_F(Dhcp4to6IpcTest, receive) {
     ASSERT_TRUE(pkt6_received);
     EXPECT_EQ("eth0", pkt6_received->getIface());
     EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
+
+    // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
+    // flag enabled.
+    EXPECT_TRUE(callback_recv_pkt_options_copy_.first);
+    EXPECT_TRUE(callback_recv_pkt_options_copy_.second);
 }
 
 // This test verifies that message with multiple DHCPv4 query options
@@ -326,6 +362,11 @@ TEST_F(Dhcp4to6IpcTest, process) {
     EXPECT_EQ("eth0", pkt6_sent->getIface());
     EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText());
 
+    // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
+    // flag enabled.
+    EXPECT_TRUE(callback_sent_pkt_options_copy_.first);
+    EXPECT_TRUE(callback_sent_pkt_options_copy_.second);
+
     // Verify the 4o6 part
     OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG);
     ASSERT_EQ(1, sent_msgs.size());

+ 80 - 0
src/bin/dhcp4/tests/hooks_unittest.cc

@@ -200,6 +200,10 @@ public:
         callout_handle.getArgument("query4", callback_qry_pkt4_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
         return (0);
     }
 
@@ -245,6 +249,11 @@ public:
         callout_handle.getArgument("query4", callback_qry_pkt4_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -312,6 +321,15 @@ public:
         callout_handle.getArgument("query4", callback_qry_pkt4_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
+        if (callback_resp_pkt4_) {
+            callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -375,6 +393,11 @@ public:
         callout_handle.getArgument("response4", callback_resp_pkt4_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_resp_pkt4_) {
+            callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -417,6 +440,11 @@ public:
         callout_handle.getArgument("subnet4collection", callback_subnet4collection_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -454,6 +482,11 @@ public:
         callout_handle.getArgument("lease4", callback_lease4_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -471,6 +504,11 @@ public:
         callout_handle.getArgument("clientid", callback_clientid_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -484,6 +522,10 @@ public:
         callout_handle.getArgument("query4", callback_qry_pkt4_);
         callout_handle.getArgument("lease4", callback_lease4_);
 
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -509,6 +551,8 @@ public:
         callback_subnet4_.reset();
         callback_subnet4collection_ = NULL;
         callback_argument_names_.clear();
+        callback_qry_options_copy_ = false;
+        callback_resp_options_copy_ = false;
     }
 
     /// pointer to Dhcpv4Srv that is used in tests
@@ -542,6 +586,15 @@ public:
 
     /// A list of all received arguments
     static vector<string> callback_argument_names_;
+
+    /// Flag indicating if copying retrieved options was enabled for
+    /// a query during callout execution.
+    static bool callback_qry_options_copy_;
+
+    /// Flag indicating if copying retrieved options was enabled for
+    /// a response during callout execution.
+    static bool callback_resp_options_copy_;
+
 };
 
 // The following fields are used in testing pkt4_receive_callout.
@@ -555,6 +608,8 @@ ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_;
 Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_;
 const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
 vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
+bool HooksDhcpv4SrvTest::callback_qry_options_copy_;
+bool HooksDhcpv4SrvTest::callback_resp_options_copy_;
 
 /// @brief Fixture class used to do basic library load/unload tests
 class LoadUnloadDhcpv4SrvTest : public ::testing::Test {
@@ -623,6 +678,9 @@ TEST_F(HooksDhcpv4SrvTest, Buffer4ReceiveSimple) {
     expected_argument_names.push_back(string("query4"));
 
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Checks if callouts installed on buffer4_receive is able to change
@@ -728,6 +786,9 @@ TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSimple) {
     expected_argument_names.push_back(string("query4"));
 
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Checks if callouts installed on pkt4_received is able to change
@@ -865,6 +926,10 @@ TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) {
     sort(callback_argument_names_.begin(), callback_argument_names_.end());
     sort(expected_argument_names.begin(), expected_argument_names.end());
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+    EXPECT_TRUE(callback_resp_options_copy_);
 }
 
 // Checks if callouts installed on pkt4_send is able to change
@@ -1006,6 +1071,9 @@ TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) {
     vector<string> expected_argument_names;
     expected_argument_names.push_back(string("response4"));
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_resp_options_copy_);
 }
 
 // Checks if callouts installed on buffer4_send are indeed called and that
@@ -1139,6 +1207,9 @@ TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
     // Compare that the available subnets are reported as expected
     EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet4collection_)[0].get());
     EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet4collection_)[1].get());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test checks if callout installed on subnet4_select hook point can pick
@@ -1300,6 +1371,9 @@ TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) {
     EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
 
     EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test verifies that a callout installed on lease4_renew can trigger
@@ -1456,6 +1530,9 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
     sort(callback_argument_names_.begin(), callback_argument_names_.end());
     sort(expected_argument_names.begin(), expected_argument_names.end());
     EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test verifies that skip flag returned by a callout installed on the
@@ -1564,6 +1641,9 @@ TEST_F(HooksDhcpv4SrvTest, HooksDecline) {
     // the lease manager) all match.
     EXPECT_EQ(addr, from_mgr->addr_);
     EXPECT_EQ(addr, callback_lease4_->addr_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Checks that decline4 hook is able to drop the packet.

+ 51 - 0
src/bin/dhcp6/dhcp6_hooks.dox

@@ -333,4 +333,55 @@ packet processing. Hook points that are not specific to packet processing
   expired leases will remain in the database and their recovery will
   be attempted during the next reclaim cycle.
 
+@section dhcpv6HooksOptionsAccess Accessing DHCPv6 Options within a Packet
+When the server constructs a response message to a client it includes
+DHCP options configured for this client in a response message. Apart
+from the dynamically created options, such as IA_NA or ClientFQDN, it
+typically includes many options specified in the server configuration
+and held within the configuration structures by @c CfgMgr. Option
+instances are created once, during server configuration, and the
+@c CfgMgr holds pointers to those instances until the next server
+reconfiguration.
+
+When the server includes an option in a response message it copies
+a pointer to the instance of this option, rather than entire option.
+This ensures the good performance of response message creation. However,
+it also implies that any modification to the option carried in the
+DHCP response will affect an instance of this option in the server
+configuration structures. This is obviously not desired as it would
+affect all subsequent DHCP transactions involving this option. The
+DHCP server code avoids modifying the options included in the messages
+so it is possible to ensure good performance without a risk of
+accidentally modifying server configuration. The situation is
+different with hooks libraries which purpose is, in many cases,
+to modify values of options inserted by the server.
+
+Thus, @c Pkt class provides a mechanism to return a copy of an
+option to a caller (e.g. a callout), rather than an instance
+shared with the @c CfgMgr. This mechanism is enabled for all instances
+of @c Pkt6 passed to the callouts, i.e. "query6" and "response6"
+arguments. It is also automatically disabled when the callout
+returns the control back to the server.
+
+At every hook point, where the server passes an instance of a packet
+to the callouts, the server calls
+@c isc::dhcp::Pkt6::setCopyRetrievedOptions (true)
+to force copying options retrieved by @c isc::dhcp::Pkt6::getOption,
+@c isc::dhcp::Pkt6::getOptions, @c isc::dhcp::Pkt6::getRelayOption
+and @c isc::dhcp::Pkt6::getAnyRelayOption within callouts. The copied
+option replaces an original option within the packet and any
+modification to the option content by the callout would only affect
+the option instance associated with the packet.
+
+On the other hand, copying each retrieved option may be expensive.
+If performance of a hook library is a concern, it is possible for the
+hook library to disable copying retrieved options by calling
+@c isc::dhcp::Pkt6::setCopyRetrievedOptions (false) within a callout.
+In this case however, the hook library implementer must be aware that
+any modification of the option instance would affect the server
+configuration and may disrupt server's operation. Thus, disabling
+copying of retrieved options is not recommended unless the hook
+library is not intended to modify configured options carried
+within a packet.
+
 */

+ 24 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -438,6 +438,9 @@ void Dhcpv6Srv::run_one() {
             // Delete previously set arguments
             callout_handle->deleteAllArguments();
 
+            // Enable copying options from the packet within hook library.
+            ScopedEnableOptionsCopy<Pkt6> response6_options_copy(rsp);
+
             // Pass incoming packet as argument
             callout_handle->setArgument("response6", rsp);
 
@@ -480,6 +483,9 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
     if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
+
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
@@ -575,6 +581,9 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
+
         // Pass incoming packet as argument
         callout_handle->setArgument("query6", query);
 
@@ -717,6 +726,9 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
     if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
+        // Enable copying options from the packets within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query_resp_options_copy(query, rsp);
+
         // Delete all previous arguments
         callout_handle->deleteAllArguments();
 
@@ -1014,6 +1026,9 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         // We're reusing callout_handle from previous calls
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(question);
+
         // Set new arguments
         callout_handle->setArgument("query6", question);
         callout_handle->setArgument("subnet6", subnet);
@@ -2023,6 +2038,9 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
     if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_release_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
+
         // Delete all previous arguments
         callout_handle->deleteAllArguments();
 
@@ -2184,6 +2202,9 @@ Dhcpv6Srv::releaseIA_PD(const DuidPtr& duid, const Pkt6Ptr& query,
         // Delete all previous arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);
+
         // Pass the original packet
         callout_handle->setArgument("query6", query);
 
@@ -2706,6 +2727,9 @@ Dhcpv6Srv::declineLease(const Pkt6Ptr& decline, const Lease6Ptr lease,
         // Delete previously set arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(decline);
+
         // Pass incoming packet as argument
         callout_handle->setArgument("query6", decline);
         callout_handle->setArgument("lease6", lease);

+ 3 - 0
src/bin/dhcp6/dhcp6to4_ipc.cc

@@ -107,6 +107,9 @@ void Dhcp6to4Ipc::handler() {
             // Delete previously set arguments
             callout_handle->deleteAllArguments();
 
+            // Enable copying options from the packet within hook library.
+            ScopedEnableOptionsCopy<Pkt6> response6_options_copy(pkt);
+
             // Pass incoming packet as argument
             callout_handle->setArgument("response6", pkt);
 

+ 15 - 0
src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc

@@ -55,6 +55,9 @@ public:
                         registerCallout("buffer6_send", buffer6_send_callout));
         // Let's wipe all existing statistics.
         StatsMgr::instance().removeAll();
+
+        // Reset the flag which we expect to be set in the callout.
+        callback_pkt_options_copy_ = false;
     }
 
     /// @brief Configure DHCP4o6 port.
@@ -77,12 +80,19 @@ public:
     static int
     buffer6_send_callout(CalloutHandle& callout_handle) {
         callout_handle.getArgument("response6", callback_pkt_);
+        if (callback_pkt_) {
+            callback_pkt_options_copy_ = callback_pkt_->isCopyRetrievedOptions();
+        }
         return (0);
     }
 
     /// @brief Response Pkt6 shared pointer returned in the callout
     static Pkt6Ptr callback_pkt_;
 
+    /// Flag indicating if copying retrieved options was enabled for
+    /// a received packet during callout execution.
+    static bool callback_pkt_options_copy_;
+
 private:
 
     /// @brief Provides fake configuration of interfaces.
@@ -90,6 +100,7 @@ private:
 };
 
 Pkt6Ptr Dhcp6to4IpcTest::callback_pkt_;
+bool Dhcp6to4IpcTest::callback_pkt_options_copy_;
 
 void
 Dhcp6to4IpcTest::configurePort(const uint16_t port) {
@@ -153,6 +164,10 @@ TEST_F(Dhcp6to4IpcTest, receive) {
     ASSERT_NO_THROW(src_ipc.send(pkt));
     ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
 
+    // Make sure that the received packet was configured to return copy of
+    // retrieved options within a callout.
+    EXPECT_TRUE(callback_pkt_options_copy_);
+
     // Get the forwarded packet from the callout
     Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
     ASSERT_TRUE(forwarded);

+ 214 - 0
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -147,6 +147,11 @@ public:
         callout_handle.getArgument("query6", callback_qry_pkt6_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -210,6 +215,11 @@ public:
         callout_handle.getArgument("query6", callback_qry_pkt6_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -284,6 +294,15 @@ public:
         callout_handle.getArgument("query6", callback_qry_pkt6_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
+        if (callback_resp_pkt6_) {
+            callback_resp_options_copy_ = callback_resp_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -337,6 +356,24 @@ public:
         return pkt6_send_callout(callout_handle);
     }
 
+    /// @brief Test callback that stores response packet.
+    /// @param callout_handle handle passed by the hooks framework.
+    /// @return always 0
+    static int
+    buffer6_send_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("buffer6_send");
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+
+        callout_handle.getArgument("response6", callback_resp_pkt6_);
+
+        if (callback_resp_pkt6_) {
+            callback_resp_options_copy_ = callback_resp_pkt6_->isCopyRetrievedOptions();
+        }
+
+        return (0);
+    }
+
     /// Test callback that stores received callout name and subnet6 values
     /// @param callout_handle handle passed by the hooks framework
     /// @return always 0
@@ -349,6 +386,11 @@ public:
         callout_handle.getArgument("subnet6collection", callback_subnet6collection_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -387,6 +429,11 @@ public:
         callout_handle.getArgument("ia_na", callback_ia_na_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -454,6 +501,11 @@ public:
         callout_handle.getArgument("ia_na", callback_ia_na_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -523,6 +575,11 @@ public:
         callout_handle.getArgument("lease6", callback_lease6_);
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -550,6 +607,10 @@ public:
         callout_handle.getArgument("query6", callback_qry_pkt6_);
         callout_handle.getArgument("lease6", callback_lease6_);
 
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -585,6 +646,8 @@ public:
         callback_ia_na_.reset();
         callback_subnet6collection_ = NULL;
         callback_argument_names_.clear();
+        callback_qry_options_copy_ = false;
+        callback_resp_options_copy_ = false;
     }
 
     /// Pointer to Dhcpv6Srv that is used in tests
@@ -615,6 +678,14 @@ public:
 
     /// A list of all received arguments
     static vector<string> callback_argument_names_;
+
+    /// Flag indicating if copying retrieved options was enabled for
+    /// a query during callout execution.
+    static bool callback_qry_options_copy_;
+
+    /// Flag indicating if copying retrieved options was enabled for
+    /// a response during callout execution.
+    static bool callback_resp_options_copy_;
 };
 
 // The following parameters are used by callouts to override
@@ -635,6 +706,8 @@ const Subnet6Collection* HooksDhcpv6SrvTest::callback_subnet6collection_;
 vector<string> HooksDhcpv6SrvTest::callback_argument_names_;
 Lease6Ptr HooksDhcpv6SrvTest::callback_lease6_;
 boost::shared_ptr<Option6IA> HooksDhcpv6SrvTest::callback_ia_na_;
+bool HooksDhcpv6SrvTest::callback_qry_options_copy_;
+bool HooksDhcpv6SrvTest::callback_resp_options_copy_;
 
 /// @brief Fixture class used to do basic library load/unload tests
 class LoadUnloadDhcpv6SrvTest : public ::testing::Test {
@@ -702,6 +775,9 @@ TEST_F(HooksDhcpv6SrvTest, simpleBuffer6Receive) {
     expected_argument_names.push_back(string("query6"));
 
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Checks if callouts installed on buffer6_receive is able to change
@@ -823,6 +899,9 @@ TEST_F(HooksDhcpv6SrvTest, simplePkt6Receive) {
     expected_argument_names.push_back(string("query6"));
 
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Checks if callouts installed on pkt6_received is able to change
@@ -947,6 +1026,10 @@ TEST_F(HooksDhcpv6SrvTest, simplePkt6Send) {
     expected_argument_names.push_back(string("query6"));
     expected_argument_names.push_back(string("response6"));
     EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+    EXPECT_TRUE(callback_resp_options_copy_);
 }
 
 // Checks if callouts installed on pkt6_send is able to change
@@ -1048,6 +1131,46 @@ TEST_F(HooksDhcpv6SrvTest, skipPkt6Send) {
     EXPECT_EQ(0, sent->getBuffer().getLength());
 }
 
+// Checks if callouts installed on buffer6_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv6SrvTest, simpleBuffer6Send) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "buffer6_send", buffer6_send_callout));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("buffer6_send", callback_name_);
+
+    // Check that there is one packet sent
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+
+    // Check that pkt6 argument passing was successful and returned proper
+    // values
+    EXPECT_TRUE(callback_resp_pkt6_.get() == adv.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("response6"));
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_resp_options_copy_);
+}
+
 // This test checks if subnet6_select callout is triggered and reports
 // valid parameters
 TEST_F(HooksDhcpv6SrvTest, subnet6Select) {
@@ -1119,6 +1242,9 @@ TEST_F(HooksDhcpv6SrvTest, subnet6Select) {
     // Compare that the available subnets are reported as expected
     EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet6collection_)[0].get());
     EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet6collection_)[1].get());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test checks if callout installed on subnet6_select hook point can pick
@@ -1291,6 +1417,9 @@ TEST_F(HooksDhcpv6SrvTest, basicLease6Renew) {
     // Check that the returned lease6 in callout is the same as the one in the
     // database
     EXPECT_TRUE(*callback_lease6_ == *l);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test verifies that incoming (positive) RENEW can be handled properly,
@@ -1537,6 +1666,88 @@ TEST_F(HooksDhcpv6SrvTest, basicLease6Release) {
     l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
                                               subnet_->getID());
     ASSERT_FALSE(l);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+}
+
+// This is a variant of the previous test that tests that callouts are
+// properly invoked for the prefix release case.
+TEST_F(HooksDhcpv6SrvTest, basicLease6ReleasePD) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_release", lease6_release_callout));
+
+    const IOAddress prefix("2001:db8:1:2:1::");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the prefix we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(),
+                               HWAddrPtr(), 80));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
+                                                        prefix);
+    ASSERT_TRUE(l);
+
+    // Let's create a RELEASE
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, iaid, 1500, 3000);
+
+    OptionPtr released_addr_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix, 80,
+                                                    300, 500));
+    ia->addOption(released_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RELEASE
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRelease(req);
+
+    ASSERT_TRUE(reply);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("lease6_release", callback_name_);
+
+    // Check that appropriate parameters are passed to the callouts
+    EXPECT_TRUE(callback_qry_pkt6_);
+    EXPECT_TRUE(callback_lease6_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("lease6");
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Check that the lease is really gone in the database
+    // get lease by address
+    l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+    ASSERT_FALSE(l);
+
+    // Get lease by subnetid/duid/iaid combination
+    l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, *duid_, iaid,
+                                              subnet_->getID());
+    ASSERT_FALSE(l);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test verifies that incoming (positive) RELEASE can be handled properly,
@@ -1907,6 +2118,9 @@ TEST_F(HooksDhcpv6SrvTest, basicLease6Decline) {
     // And that the parameters passed to callout are consistent with the database
     EXPECT_EQ(addr, from_mgr->addr_);
     EXPECT_EQ(addr, callback_lease6_->addr_);
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // Test that the lease6_decline hook point can handle SKIP status.

+ 1 - 1
src/bin/lfc/.gitignore

@@ -2,4 +2,4 @@
 /kea-lfc.8
 /lfc_messages.cc
 /lfc_messages.h
-/s-messages
+/s-messages

+ 1 - 1
src/bin/perfdhcp/.gitignore

@@ -1,2 +1,2 @@
 /perfdhcp
-/perfdhcp.1
+/perfdhcp.8

+ 2 - 2
src/bin/perfdhcp/localized_option.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -126,7 +126,7 @@ public:
     /// \brief Checks if option is valid.
     ///
     /// \return true, if option is valid.
-    virtual bool valid() {
+    virtual bool valid() const {
         return (Option::valid() && option_valid_);
     }
 

+ 1 - 0
src/lib/cfgrpt/tests/.gitignore

@@ -0,0 +1 @@
+/run_unittests

+ 55 - 17
src/lib/dhcp/option.cc

@@ -53,8 +53,32 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
     check();
 }
 
+Option::Option(const Option& option)
+    : universe_(option.universe_), type_(option.type_),
+      data_(option.data_), options_(),
+      encapsulated_space_(option.encapsulated_space_) {
+    option.getOptionsCopy(options_);
+}
+
+Option&
+Option::operator=(const Option& rhs) {
+    if (&rhs != this) {
+        universe_ = rhs.universe_;
+        type_ = rhs.type_;
+        data_ = rhs.data_;
+        rhs.getOptionsCopy(options_);
+        encapsulated_space_ = rhs.encapsulated_space_;
+    }
+    return (*this);
+}
+
+OptionPtr
+Option::clone() const {
+    return (cloneInternal<Option>());
+}
+
 void
-Option::check() {
+Option::check() const {
     if ( (universe_ != V4) && (universe_ != V6) ) {
         isc_throw(BadValue, "Invalid universe type specified. "
                   << "Only V4 and V6 are allowed.");
@@ -77,7 +101,7 @@ Option::check() {
     // both types and data size.
 }
 
-void Option::pack(isc::util::OutputBuffer& buf) {
+void Option::pack(isc::util::OutputBuffer& buf) const {
     // Write a header.
     packHeader(buf);
     // Write data.
@@ -89,7 +113,7 @@ void Option::pack(isc::util::OutputBuffer& buf) {
 }
 
 void
-Option::packHeader(isc::util::OutputBuffer& buf) {
+Option::packHeader(isc::util::OutputBuffer& buf) const {
     if (universe_ == V4) {
         if (len() > 255) {
             isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
@@ -109,7 +133,7 @@ Option::packHeader(isc::util::OutputBuffer& buf) {
 }
 
 void
-Option::packOptions(isc::util::OutputBuffer& buf) {
+Option::packOptions(isc::util::OutputBuffer& buf) const {
     switch (universe_) {
     case V4:
         LibDHCP::packOptions4(buf, options_);
@@ -141,7 +165,7 @@ Option::unpackOptions(const OptionBuffer& buf) {
     }
 }
 
-uint16_t Option::len() {
+uint16_t Option::len() const {
     // Returns length of the complete option (data length + DHCPv4/DHCPv6
     // option header)
 
@@ -149,7 +173,7 @@ uint16_t Option::len() {
     size_t length = getHeaderLen() + data_.size();
 
     // ... and sum of lengths of all suboptions
-    for (OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -162,7 +186,7 @@ uint16_t Option::len() {
 }
 
 bool
-Option::valid() {
+Option::valid() const {
     if (universe_ != V4 &&
         universe_ != V6) {
         return (false);
@@ -171,7 +195,7 @@ Option::valid() {
     return (true);
 }
 
-OptionPtr Option::getOption(uint16_t opt_type) {
+OptionPtr Option::getOption(uint16_t opt_type) const {
     isc::dhcp::OptionCollection::const_iterator x =
         options_.find(opt_type);
     if ( x != options_.end() ) {
@@ -180,6 +204,20 @@ OptionPtr Option::getOption(uint16_t opt_type) {
     return OptionPtr(); // NULL
 }
 
+void
+Option::getOptionsCopy(OptionCollection& options_copy) const {
+    OptionCollection local_options;
+    for (OptionCollection::const_iterator it = options_.begin();
+         it != options_.end(); ++it) {
+        OptionPtr copy = it->second->clone();
+        local_options.insert(std::make_pair(it->second->getType(),
+                                            copy));
+    }
+    // All options copied successfully, so assign them to the output
+    // parameter.
+    options_copy.swap(local_options);
+}
+
 bool Option::delOption(uint16_t opt_type) {
     isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
     if ( x != options_.end() ) {
@@ -190,7 +228,7 @@ bool Option::delOption(uint16_t opt_type) {
 }
 
 
-std::string Option::toText(int indent) {
+std::string Option::toText(int indent) const {
     std::stringstream output;
     output << headerToText(indent) << ": ";
 
@@ -209,13 +247,13 @@ std::string Option::toText(int indent) {
 }
 
 std::string
-Option::toString() {
+Option::toString() const {
     /// @todo: Implement actual conversion in derived classes.
     return (toText(0));
 }
 
 std::vector<uint8_t>
-Option::toBinary(const bool include_header) {
+Option::toBinary(const bool include_header) const {
     OutputBuffer buf(len());
     try {
         // If the option is too long, exception will be thrown. We allow
@@ -236,7 +274,7 @@ Option::toBinary(const bool include_header) {
 }
 
 std::string
-Option::toHexString(const bool include_header) {
+Option::toHexString(const bool include_header) const {
     // Prepare binary version of the option.
     std::vector<uint8_t> option_vec = toBinary(include_header);
 
@@ -250,7 +288,7 @@ Option::toHexString(const bool include_header) {
 }
 
 std::string
-Option::headerToText(const int indent, const std::string& type_name) {
+Option::headerToText(const int indent, const std::string& type_name) const {
     std::stringstream output;
     for (int i = 0; i < indent; i++)
         output << " ";
@@ -284,7 +322,7 @@ Option::suboptionsToText(const int indent) const {
 }
 
 uint16_t
-Option::getHeaderLen() {
+Option::getHeaderLen() const {
     switch (universe_) {
     case V4:
         return OPTION4_HDR_LEN; // header length for v4
@@ -305,7 +343,7 @@ void Option::addOption(OptionPtr opt) {
     options_.insert(make_pair(opt->getType(), opt));
 }
 
-uint8_t Option::getUint8() {
+uint8_t Option::getUint8() const {
     if (data_.size() < sizeof(uint8_t) ) {
         isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_
                   << " that has size " << data_.size());
@@ -313,12 +351,12 @@ uint8_t Option::getUint8() {
     return (data_[0]);
 }
 
-uint16_t Option::getUint16() {
+uint16_t Option::getUint16() const {
     // readUint16() checks and throws OutOfRange if data_ is too small.
     return (readUint16(&data_[0], data_.size()));
 }
 
-uint32_t Option::getUint32() {
+uint32_t Option::getUint32() const {
     // readUint32() checks and throws OutOfRange if data_ is too small.
     return (readUint32(&data_[0], data_.size()));
 }

+ 70 - 20
src/lib/dhcp/option.h

@@ -144,6 +144,33 @@ public:
     Option(Universe u, uint16_t type, OptionBufferConstIter first,
            OptionBufferConstIter last);
 
+    /// @brief Copy constructor.
+    ///
+    /// This constructor makes a deep copy of the option and all of the
+    /// suboptions. It calls @ref getOptionsCopy to deep copy suboptions.
+    ///
+    /// @param source Option to be copied.
+    Option(const Option& source);
+
+    /// @brief Assignment operator.
+    ///
+    /// The assignment operator performs a deep copy of the option and
+    /// its suboptions. It calls @ref getOptionsCopy to deep copy
+    /// suboptions.
+    ///
+    /// @param rhs Option to be assigned.
+    Option& operator=(const Option& rhs);
+
+    /// @brief Copies this option and returns a pointer to the copy.
+    ///
+    /// This function must be overridden in the derived classes to make
+    /// a copy of the derived type. The simplest way to do it is by
+    /// calling @ref cloneInternal function with an appropriate template
+    /// parameter.
+    ///
+    /// @return Pointer to the copy of the option.
+    virtual OptionPtr clone() const;
+
     /// @brief returns option universe (V4 or V6)
     ///
     /// @return universe type
@@ -158,7 +185,7 @@ public:
     /// @param buf pointer to a buffer
     ///
     /// @throw BadValue Universe of the option is neither V4 nor V6.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
     ///
@@ -172,15 +199,15 @@ public:
     /// @param indent number of spaces before printing text
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns string representation of the value
     ///
-    /// This is terse repesentation used in cases where client classification
+    /// This is terse representation used in cases where client classification
     /// refers to a specific option.
     ///
     /// @return string that represents the value of the option.
-    virtual std::string toString();
+    virtual std::string toString() const;
 
     /// @brief Returns binary representation of the option.
     ///
@@ -189,7 +216,7 @@ public:
     /// header fields.
     ///
     /// @return Vector holding binary representation of the option.
-    virtual std::vector<uint8_t> toBinary(const bool include_header = false);
+    virtual std::vector<uint8_t> toBinary(const bool include_header = false) const;
 
     /// @brief Returns string containing hexadecimal representation of option.
     ///
@@ -198,7 +225,7 @@ public:
     /// header fields.
     ///
     /// @return String containing hexadecimal representation of the option.
-    virtual std::string toHexString(const bool include_header = false);
+    virtual std::string toHexString(const bool include_header = false) const;
 
     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
     ///
@@ -209,17 +236,17 @@ public:
     /// option header)
     ///
     /// @return length of the option
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns length of header (2 for v4, 4 for v6)
     ///
     /// @return length of option header
-    virtual uint16_t getHeaderLen();
+    virtual uint16_t getHeaderLen() const;
 
     /// returns if option is valid (e.g. option may be truncated)
     ///
     /// @return true, if option is valid
-    virtual bool valid();
+    virtual bool valid() const;
 
     /// Returns pointer to actual data.
     ///
@@ -245,8 +272,8 @@ public:
     ///
     /// @param type type of requested suboption
     ///
-    /// @return shared_ptr to requested suoption
-    OptionPtr getOption(uint16_t type);
+    /// @return shared_ptr to requested suboption
+    OptionPtr getOption(uint16_t type) const;
 
     /// @brief Returns all encapsulated options.
     ///
@@ -257,6 +284,13 @@ public:
         return (options_);
     }
 
+    /// @brief Performs deep copy of suboptions.
+    ///
+    /// This method calls @ref clone method to deep copy each option.
+    ///
+    /// @param [out] options_copy Container where copied options are stored.
+    void getOptionsCopy(OptionCollection& options_copy) const;
+
     /// Attempts to delete first suboption of requested type
     ///
     /// @param type Type of option to be deleted.
@@ -269,21 +303,21 @@ public:
     /// @throw isc::OutOfRange Thrown if the option has a length of 0.
     ///
     /// @return value of the first byte
-    uint8_t getUint8();
+    uint8_t getUint8() const;
 
     /// @brief Returns content of first word.
     ///
     /// @throw isc::OutOfRange Thrown if the option has a length less than 2.
     ///
     /// @return uint16_t value stored on first two bytes
-    uint16_t getUint16();
+    uint16_t getUint16() const;
 
     /// @brief Returns content of first double word.
     ///
     /// @throw isc::OutOfRange Thrown if the option has a length less than 4.
     ///
     /// @return uint32_t value stored on first four bytes
-    uint32_t getUint32();
+    uint32_t getUint32() const;
 
     /// @brief Sets content of this option to singe uint8 value.
     ///
@@ -341,8 +375,8 @@ public:
     /// @brief Checks if options are equal.
     ///
     /// This method calls a virtual @c equals function to compare objects.
-    /// This method is not meant to be overriden in the derived classes.
-    /// Instead, the other @c equals function must be overriden.
+    /// This method is not meant to be overridden in the derived classes.
+    /// Instead, the other @c equals function must be overridden.
     ///
     /// @param other Pointer to the option to compare this option to.
     /// @return true if both options are equal, false otherwise.
@@ -364,6 +398,22 @@ public:
 
 protected:
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    ///
+    /// The deep copy of the option is performed by calling copy
+    /// constructor of the option of a given type. Derived classes call
+    /// this method in the implementations of @ref clone methods to
+    /// create a copy of the option of their type.
+    ///
+    /// @tparam OptionType Type of the option of which a clone should
+    /// be created.
+    template<typename OptionType>
+    OptionPtr cloneInternal() const {
+        boost::shared_ptr<OptionType>
+            option(new OptionType(*dynamic_cast<const OptionType*>(this)));
+        return (option);
+    }
+
     /// @brief Store option's header in a buffer.
     ///
     /// This method writes option's header into a buffer in the
@@ -378,7 +428,7 @@ protected:
     /// directly by other classes.
     ///
     /// @param [out] buf output buffer.
-    void packHeader(isc::util::OutputBuffer& buf);
+    void packHeader(isc::util::OutputBuffer& buf) const;
 
     /// @brief Store sub options in a buffer.
     ///
@@ -393,7 +443,7 @@ protected:
     /// exceptions thrown by pack methods invoked on objects
     /// representing sub options. We should consider whether to aggregate
     /// those into one exception which can be documented here.
-    void packOptions(isc::util::OutputBuffer& buf);
+    void packOptions(isc::util::OutputBuffer& buf) const;
 
     /// @brief Builds a collection of sub options from the buffer.
     ///
@@ -420,7 +470,7 @@ protected:
     ///
     /// @return Option header in the textual format.
     std::string headerToText(const int indent = 0,
-                             const std::string& type_name = "");
+                             const std::string& type_name = "") const;
 
     /// @brief Returns collection of suboptions in the textual format.
     ///
@@ -441,7 +491,7 @@ protected:
     /// It is used in constructors. In there are any problems detected
     /// (like specifying type > 255 for DHCPv4 option), it will throw
     /// BadValue or OutOfRange exceptions.
-    void check();
+    void check() const;
 
     /// option universe (V4 or V6)
     Universe universe_;

+ 9 - 4
src/lib/dhcp/option4_addrlst.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -55,8 +55,13 @@ Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
     setAddress(addr);
 }
 
+OptionPtr
+Option4AddrLst::clone() const {
+    return (cloneInternal<Option4AddrLst>());
+}
+
 void
-Option4AddrLst::pack(isc::util::OutputBuffer& buf) {
+Option4AddrLst::pack(isc::util::OutputBuffer& buf) const {
 
     if (addrs_.size() * V4ADDRESS_LEN > 255) {
         isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ << " is too big."
@@ -106,13 +111,13 @@ void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) {
     addrs_.push_back(addr);
 }
 
-uint16_t Option4AddrLst::len() {
+uint16_t Option4AddrLst::len() const {
 
     // Returns length of the complete option (option header + data length)
     return (getHeaderLen() + addrs_.size() * V4ADDRESS_LEN);
 }
 
-std::string Option4AddrLst::toText(int indent) {
+std::string Option4AddrLst::toText(int indent) const {
     std::stringstream output;
     output << headerToText(indent) << ":";
 

+ 7 - 4
src/lib/dhcp/option4_addrlst.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -80,25 +80,28 @@ public:
     Option4AddrLst(uint8_t type, OptionBufferConstIter first,
                    OptionBufferConstIter last);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in a wire-format to a buffer.
     ///
     /// Method will throw if option storing fails for some reason.
     ///
     /// @param buf output buffer (option will be stored there)
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// Returns string representation of the option.
     ///
     /// @param indent number of spaces before printing text
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// Returns length of the complete option (data length + DHCPv4/DHCPv6
     /// option header)
     ///
     /// @return length of the option
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns vector with addresses.
     ///

+ 15 - 4
src/lib/dhcp/option4_client_fqdn.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -346,11 +346,17 @@ Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
       impl_(new Option4ClientFqdnImpl(*source.impl_)) {
 }
 
+OptionPtr
+Option4ClientFqdn::clone() const {
+    return (cloneInternal<Option4ClientFqdn>());
+}
+
 Option4ClientFqdn&
 // This assignment operator handles assignment to self, it uses copy
 // constructor of Option4ClientFqdnImpl to copy all required values.
 // cppcheck-suppress operatorEqToSelf
 Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+    Option::operator=(source);
     Option4ClientFqdnImpl* old_impl = impl_;
     impl_ = new Option4ClientFqdnImpl(*source.impl_);
     delete(old_impl);
@@ -396,6 +402,11 @@ Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
     impl_->flags_ = new_flag;
 }
 
+std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
+Option4ClientFqdn::getRcode() const {
+    return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
+}
+
 void
 Option4ClientFqdn::setRcode(const Rcode& rcode) {
     impl_->rcode1_ = rcode;
@@ -463,7 +474,7 @@ Option4ClientFqdn::getDomainNameType() const {
 }
 
 void
-Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) const {
     // Header = option code and length.
     packHeader(buf);
     // Flags field.
@@ -487,7 +498,7 @@ Option4ClientFqdn::unpack(OptionBufferConstIter first,
 }
 
 std::string
-Option4ClientFqdn::toText(int indent) {
+Option4ClientFqdn::toText(int indent) const {
     std::ostringstream stream;
     std::string in(indent, ' '); // base indentation
     stream << in  << "type=" << type_ << " (CLIENT_FQDN), "
@@ -504,7 +515,7 @@ Option4ClientFqdn::toText(int indent) {
 }
 
 uint16_t
-Option4ClientFqdn::len() {
+Option4ClientFqdn::len() const {
     uint16_t domain_name_length = 0;
     // Try to calculate the length of the domain name only if there is
     // any domain name specified.

+ 15 - 5
src/lib/dhcp/option4_client_fqdn.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
 #include <dns/name.h>
 
 #include <string>
+#include <utility>
 
 namespace isc {
 namespace dhcp {
@@ -215,9 +216,12 @@ public:
     explicit Option4ClientFqdn(OptionBufferConstIter first,
                                OptionBufferConstIter last);
 
-   /// @brief Copy constructor
+    /// @brief Copy constructor
     Option4ClientFqdn(const Option4ClientFqdn& source);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Destructor
     virtual ~Option4ClientFqdn();
 
@@ -251,6 +255,12 @@ public:
     /// @brief Sets the flag field value to 0.
     void resetFlags();
 
+    /// @brief Returns @c Rcode objects representing value of RCODE1 and RCODE2.
+    ///
+    /// @return Pair of Rcode objects of which first is the RCODE1 and the
+    /// second is RCODE2.
+    std::pair<Rcode, Rcode> getRcode() const;
+
     /// @brief Set Rcode value.
     ///
     /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
@@ -299,7 +309,7 @@ public:
    /// @brief Writes option in the wire format into a buffer.
     ///
     /// @param [out] buf output buffer where option data will be stored.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses option from the received buffer.
     ///
@@ -322,13 +332,13 @@ public:
     /// @param indent number of spaces before printed text.
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns length of the complete option (data length +
     /// DHCPv4 option header).
     ///
     /// @return length of the option.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     ///
     /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option

+ 9 - 4
src/lib/dhcp/option6_addrlst.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -41,6 +41,11 @@ Option6AddrLst::Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6AddrLst::clone() const {
+    return (cloneInternal<Option6AddrLst>());
+}
+
 void
 Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
     if (!addr.isV6()) {
@@ -56,7 +61,7 @@ Option6AddrLst::setAddresses(const AddressContainer& addrs) {
     addrs_ = addrs;
 }
 
-void Option6AddrLst::pack(isc::util::OutputBuffer& buf) {
+void Option6AddrLst::pack(isc::util::OutputBuffer& buf) const {
 
     buf.writeUint16(type_);
 
@@ -89,7 +94,7 @@ void Option6AddrLst::unpack(OptionBufferConstIter begin,
     }
 }
 
-std::string Option6AddrLst::toText(int indent) {
+std::string Option6AddrLst::toText(int indent) const {
     stringstream output;
     output << headerToText(indent) << ":";
 
@@ -100,7 +105,7 @@ std::string Option6AddrLst::toText(int indent) {
     return (output.str());
 }
 
-uint16_t Option6AddrLst::len() {
+uint16_t Option6AddrLst::len() const {
     return (OPTION6_HDR_LEN + addrs_.size() * V6ADDRESS_LEN);
 }
 

+ 6 - 4
src/lib/dhcp/option6_addrlst.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -45,10 +45,12 @@ public:
     Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
                    OptionBufferConstIter end);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Assembles on-wire form of this option
     ///
     /// @param buf pointer to packet buffer
-    void pack(isc::util::OutputBuffer& buf);
+    void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received data
     ///
@@ -57,7 +59,7 @@ public:
     virtual void unpack(OptionBufferConstIter begin,
                         OptionBufferConstIter end);
 
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Sets a single address.
     ///
@@ -80,7 +82,7 @@ public:
     AddressContainer getAddresses() const { return addrs_; };
 
     // returns data length (data length + DHCPv4/DHCPv6 option header)
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
 protected:
     AddressContainer addrs_;

+ 10 - 4
src/lib/dhcp/option6_client_fqdn.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -293,11 +293,17 @@ Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
       impl_(new Option6ClientFqdnImpl(*source.impl_)) {
 }
 
+OptionPtr
+Option6ClientFqdn::clone() const {
+    return (cloneInternal<Option6ClientFqdn>());
+}
+
 Option6ClientFqdn&
 // This assignment operator handles assignment to self, it uses copy
 // constructor of Option6ClientFqdnImpl to copy all required values.
 // cppcheck-suppress operatorEqToSelf
 Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+    Option::operator=(source);
     Option6ClientFqdnImpl* old_impl = impl_;
     impl_ = new Option6ClientFqdnImpl(*source.impl_);
     delete(old_impl);
@@ -395,7 +401,7 @@ Option6ClientFqdn::getDomainNameType() const {
 }
 
 void
-Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) const {
     // Header = option code and length.
     packHeader(buf);
     // Flags field.
@@ -416,7 +422,7 @@ Option6ClientFqdn::unpack(OptionBufferConstIter first,
 }
 
 std::string
-Option6ClientFqdn::toText(int indent) {
+Option6ClientFqdn::toText(int indent) const {
     std::ostringstream stream;
     std::string in(indent, ' '); // base indentation
     stream << in  << "type=" << type_ << "(CLIENT_FQDN)" << ", "
@@ -432,7 +438,7 @@ Option6ClientFqdn::toText(int indent) {
 }
 
 uint16_t
-Option6ClientFqdn::len() {
+Option6ClientFqdn::len() const {
     uint16_t domain_name_length = 0;
     if (impl_->domain_name_) {
         // If domain name is partial, the NULL terminating character

+ 8 - 5
src/lib/dhcp/option6_client_fqdn.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -137,9 +137,12 @@ public:
     explicit Option6ClientFqdn(OptionBufferConstIter first,
                                OptionBufferConstIter last);
 
-   /// @brief Copy constructor
+    /// @brief Copy constructor
     Option6ClientFqdn(const Option6ClientFqdn& source);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Destructor
     virtual ~Option6ClientFqdn();
 
@@ -217,7 +220,7 @@ public:
    /// @brief Writes option in the wire format into a buffer.
     ///
     /// @param [out] buf output buffer where option data will be stored.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses option from the received buffer.
     ///
@@ -240,13 +243,13 @@ public:
     /// @param indent number of spaces before printed text.
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns length of the complete option (data length +
     /// DHCPv6 option header).
     ///
     /// @return length of the option.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
 private:
 

+ 10 - 5
src/lib/dhcp/option6_ia.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -48,7 +48,12 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
-void Option6IA::pack(isc::util::OutputBuffer& buf) {
+OptionPtr
+Option6IA::clone() const {
+    return (cloneInternal<Option6IA>());
+}
+
+void Option6IA::pack(isc::util::OutputBuffer& buf) const {
     buf.writeUint16(type_);
     buf.writeUint16(len() - OPTION6_HDR_LEN);
     buf.writeUint32(iaid_);
@@ -76,7 +81,7 @@ void Option6IA::unpack(OptionBufferConstIter begin,
     unpackOptions(OptionBuffer(begin, end));
 }
 
-std::string Option6IA::toText(int indent) {
+std::string Option6IA::toText(int indent) const {
     stringstream output;
 
     switch(getType()) {
@@ -96,13 +101,13 @@ std::string Option6IA::toText(int indent) {
     return (output.str());
 }
 
-uint16_t Option6IA::len() {
+uint16_t Option6IA::len() const {
 
     uint16_t length = OPTION6_HDR_LEN /*header (4)*/ +
         OPTION6_IA_LEN  /* option content (12) */;
 
     // length of all suboptions
-    for (OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();

+ 8 - 6
src/lib/dhcp/option6_ia.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -39,11 +39,14 @@ public:
     Option6IA(uint16_t type, OptionBuffer::const_iterator begin,
               OptionBuffer::const_iterator end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///
     /// @param buf buffer (option will be stored here)
-    void pack(isc::util::OutputBuffer& buf);
+    void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer
     ///
@@ -58,9 +61,8 @@ public:
     ///
     /// @param indent number of leading space characters
     ///
-    /// @return string with text represenation
-    virtual std::string
-    toText(int indent = 0);
+    /// @return string with text representation
+    virtual std::string toText(int indent = 0) const;
 
     /// Sets T1 timer.
     ///
@@ -98,7 +100,7 @@ public:
     /// Returns length of this option, including option header and suboptions
     ///
     /// @return length of this option
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
 protected:
 

+ 10 - 5
src/lib/dhcp/option6_iaaddr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,7 +42,12 @@ Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
     unpack(begin, end);
 }
 
-void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
+OptionPtr
+Option6IAAddr::clone() const {
+    return (cloneInternal<Option6IAAddr>());
+}
+
+void Option6IAAddr::pack(isc::util::OutputBuffer& buf) const {
 
     buf.writeUint16(type_);
 
@@ -81,7 +86,7 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
     unpackOptions(OptionBuffer(begin, end));
 }
 
-std::string Option6IAAddr::toText(int indent) {
+std::string Option6IAAddr::toText(int indent) const {
     std::stringstream output;
     output << headerToText(indent, "IAADDR") << ": "
            << "address=" << addr_
@@ -92,14 +97,14 @@ std::string Option6IAAddr::toText(int indent) {
     return (output.str());
 }
 
-uint16_t Option6IAAddr::len() {
+uint16_t Option6IAAddr::len() const {
 
     uint16_t length = OPTION6_HDR_LEN + OPTION6_IAADDR_LEN;
 
     // length of all suboptions
     // TODO implement:
     // protected: unsigned short Option::lenHelper(int header_size);
-    for (OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();

+ 7 - 4
src/lib/dhcp/option6_iaaddr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,13 +46,16 @@ public:
     Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
                   OptionBuffer::const_iterator end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///
     /// @param buf pointer to a buffer
-    void pack(isc::util::OutputBuffer& buf);
+    void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
     ///
@@ -67,7 +70,7 @@ public:
     ///
     /// @return string with text representation.
     virtual std::string
-    toText(int indent = 0);
+    toText(int indent = 0) const;
 
 
     /// sets address in this option.
@@ -106,7 +109,7 @@ public:
     getValid() const { return valid_; }
 
     /// returns data length (data length + DHCPv4/DHCPv6 option header)
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
 protected:
     /// contains an IPv6 address

+ 10 - 5
src/lib/dhcp/option6_iaprefix.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -44,7 +44,12 @@ Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator beg
     unpack(begin, end);
 }
 
-void Option6IAPrefix::pack(isc::util::OutputBuffer& buf) {
+OptionPtr
+Option6IAPrefix::clone() const {
+    return (cloneInternal<Option6IAPrefix>());
+}
+
+void Option6IAPrefix::pack(isc::util::OutputBuffer& buf) const {
     if (!addr_.isV6()) {
         isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
     }
@@ -90,7 +95,7 @@ void Option6IAPrefix::unpack(OptionBuffer::const_iterator begin,
     unpackOptions(OptionBuffer(begin, end));
 }
 
-std::string Option6IAPrefix::toText(int indent) {
+std::string Option6IAPrefix::toText(int indent) const {
     std::stringstream output;
     output << headerToText(indent, "IAPREFIX") << ": "
            << "prefix=" << addr_ << "/" << static_cast<int>(prefix_len_)
@@ -101,7 +106,7 @@ std::string Option6IAPrefix::toText(int indent) {
     return (output.str());
 }
 
-uint16_t Option6IAPrefix::len() {
+uint16_t Option6IAPrefix::len() const {
 
     uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
 
@@ -117,7 +122,7 @@ void
 Option6IAPrefix::mask(OptionBuffer::const_iterator begin,
                       OptionBuffer::const_iterator end,
                       const uint8_t len,
-                      OptionBuffer& output_address) {
+                      OptionBuffer& output_address) const {
     output_address.resize(16, 0);
     if (len >= 128) {
         std::copy(begin, end, output_address.begin());

+ 8 - 5
src/lib/dhcp/option6_iaprefix.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -74,6 +74,9 @@ public:
     Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
                     OptionBuffer::const_iterator end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused
@@ -82,7 +85,7 @@ public:
     /// @throw BadValue if the address is not IPv6
     ///
     /// @param buf pointer to a buffer
-    void pack(isc::util::OutputBuffer& buf);
+    void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
     ///
@@ -103,7 +106,7 @@ public:
     /// @param indent number of spaces before printing text
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// sets address in this option.
     ///
@@ -115,7 +118,7 @@ public:
     uint8_t getLength() const { return prefix_len_; }
 
     /// returns data length (data length + DHCPv4/DHCPv6 option header)
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
 private:
 
@@ -130,7 +133,7 @@ private:
     void mask(OptionBuffer::const_iterator begin,
               OptionBuffer::const_iterator end,
               const uint8_t len,
-              OptionBuffer& output_address);
+              OptionBuffer& output_address) const;
 
     uint8_t prefix_len_;
 };

+ 9 - 4
src/lib/dhcp/option6_status_code.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,8 +40,13 @@ Option6StatusCode::Option6StatusCode(OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6StatusCode::clone() const {
+    return (cloneInternal<Option6StatusCode>());
+}
+
 void
-Option6StatusCode::pack(isc::util::OutputBuffer& buf) {
+Option6StatusCode::pack(isc::util::OutputBuffer& buf) const {
     // Pack option header.
     packHeader(buf);
     // Write numeric status code.
@@ -69,12 +74,12 @@ Option6StatusCode::unpack(OptionBufferConstIter begin, OptionBufferConstIter end
 }
 
 uint16_t
-Option6StatusCode::len() {
+Option6StatusCode::len() const {
     return (getHeaderLen() + sizeof(uint16_t) + status_message_.size());
 }
 
 std::string
-Option6StatusCode::toText(int indent) {
+Option6StatusCode::toText(int indent) const {
     std::ostringstream output;
     output << headerToText(indent) << ": " << dataToText();
 

+ 7 - 4
src/lib/dhcp/option6_status_code.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -37,13 +37,16 @@ public:
     /// @param end Iterator to end of option data (first byte after option end).
     Option6StatusCode(OptionBufferConstIter begin, OptionBufferConstIter end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///
     /// @param [out] buf Pointer to the output buffer.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
     ///
@@ -54,12 +57,12 @@ public:
     /// @brief Returns total length of the option.
     ///
     /// The returned length is a sum of the option header and data fields.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns textual representation of the option.
     ///
     /// @param indent Number of spaces before printing text.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns textual representation of the option data.
     ///

+ 10 - 5
src/lib/dhcp/option_custom.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,6 +40,11 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
     createBuffers(getData());
 }
 
+OptionPtr
+OptionCustom::clone() const {
+    return (cloneInternal<OptionCustom>());
+}
+
 void
 OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
     checkArrayType();
@@ -357,7 +362,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
 }
 
 void
-OptionCustom::pack(isc::util::OutputBuffer& buf) {
+OptionCustom::pack(isc::util::OutputBuffer& buf) const {
 
     // Pack DHCP header (V4 or V6).
     packHeader(buf);
@@ -494,7 +499,7 @@ OptionCustom::unpack(OptionBufferConstIter begin,
 }
 
 uint16_t
-OptionCustom::len() {
+OptionCustom::len() const {
     // The length of the option is a sum of option header ...
     size_t length = getHeaderLen();
 
@@ -505,7 +510,7 @@ OptionCustom::len() {
     }
 
     // ... and lengths of all suboptions
-    for (OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -523,7 +528,7 @@ void OptionCustom::initialize(const OptionBufferConstIter first,
     createBuffers(getData());
 }
 
-std::string OptionCustom::toText(int indent) {
+std::string OptionCustom::toText(int indent) const {
     std::stringstream output;
 
     output << headerToText(indent) << ":";

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -79,6 +79,9 @@ public:
     OptionCustom(const OptionDefinition& def, Universe u,
                  OptionBufferConstIter first, OptionBufferConstIter last);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
+
     /// @brief Create new buffer and set its value as an IP address.
     ///
     /// @param address IPv4 or IPv6 address to be written to
@@ -243,7 +246,7 @@ public:
     /// @brief Writes DHCP option in a wire format to a buffer.
     ///
     /// @param buf output buffer (option will be stored there).
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
     ///
@@ -257,13 +260,13 @@ public:
     /// @param indent number of spaces before printed text.
     ///
     /// @return string with text representation.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns length of the complete option (data length +
     ///        DHCPv4/DHCPv6 option header)
     ///
     /// @return length of the option
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Sets content of this option from buffer.
     ///

+ 14 - 5
src/lib/dhcp/option_int.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,6 +46,10 @@ typedef boost::shared_ptr<OptionUint32> OptionUint32Ptr;
 /// @param T data field type (see above).
 template<typename T>
 class OptionInt: public Option {
+private:
+
+    /// @brief Pointer to the option object for a specified type T.
+    typedef boost::shared_ptr<OptionInt<T> > OptionIntTypePtr;
 
 public:
     /// @brief Constructor.
@@ -90,6 +94,11 @@ public:
         unpack(begin, end);
     }
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const {
+        return (cloneInternal<OptionInt<T> >());
+    }
+
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///
@@ -98,7 +107,7 @@ public:
     /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
     /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
     /// because it is checked in a constructor.
-    void pack(isc::util::OutputBuffer& buf) {
+    void pack(isc::util::OutputBuffer& buf) const {
         // Pack option header.
         packHeader(buf);
         // Depending on the data type length we use different utility functions
@@ -184,13 +193,13 @@ public:
     /// Returns length of this option, including option header and suboptions
     ///
     /// @return length of this option
-    virtual uint16_t len() {
+    virtual uint16_t len() const {
         // Calculate the length of the header.
         uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
         // The data length is equal to size of T.
         length += sizeof(T);;
         // length of all suboptions
-        for (OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::const_iterator it = options_.begin();
              it != options_.end();
              ++it) {
             length += (*it).second->len();
@@ -204,7 +213,7 @@ public:
     /// The returned value also includes the suboptions if present.
     ///
     /// @param indent Number of spaces to be inserted before the text.
-    virtual std::string toText(int indent = 0) {
+    virtual std::string toText(int indent = 0) const {
         std::stringstream output;
         output << headerToText(indent) << ": ";
 

+ 15 - 5
src/lib/dhcp/option_int_array.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_data_types.h>
 #include <util/io_utilities.h>
+#include <boost/shared_ptr.hpp>
 #include <sstream>
 #include <stdint.h>
 
@@ -53,6 +54,10 @@ typedef boost::shared_ptr<OptionUint32Array> OptionUint32ArrayPtr;
 /// @param T data field type (see above).
 template<typename T>
 class OptionIntArray: public Option {
+private:
+
+    /// @brief Pointer to the option type for the specified T.
+    typedef boost::shared_ptr<OptionIntArray<T> > OptionIntArrayTypePtr;
 
 public:
 
@@ -116,6 +121,11 @@ public:
         unpack(begin, end);
     }
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const {
+        return (cloneInternal<OptionIntArray<T> >());
+    }
+
     /// @brief Adds a new value to the array.
     ///
     /// @param value a value being added.
@@ -131,7 +141,7 @@ public:
     /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
     /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
     /// because it is checked in a constructor.
-    void pack(isc::util::OutputBuffer& buf) {
+    void pack(isc::util::OutputBuffer& buf) const {
         // Pack option header.
         packHeader(buf);
         // Pack option data.
@@ -229,11 +239,11 @@ public:
     /// Returns length of this option, including option header and suboptions
     ///
     /// @return length of this option
-    virtual uint16_t len() {
+    virtual uint16_t len() const {
         uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
         length += values_.size() * sizeof(T);
         // length of all suboptions
-        for (OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::const_iterator it = options_.begin();
              it != options_.end();
              ++it) {
             length += (*it).second->len();
@@ -247,7 +257,7 @@ public:
     /// the text.
     ///
     /// @return textual representation of the option.
-    virtual std::string toText(int indent = 0) {
+    virtual std::string toText(int indent = 0) const {
         std::stringstream output;
         output << headerToText(indent) << ":";
 

+ 9 - 4
src/lib/dhcp/option_opaque_data_tuples.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,8 +25,13 @@ OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionOpaqueDataTuples::clone() const {
+    return (cloneInternal<OptionOpaqueDataTuples>());
+}
+
 void
-OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf) {
+OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);
 
     for (TuplesCollection::const_iterator it = tuples_.begin();
@@ -107,7 +112,7 @@ OptionOpaqueDataTuples::hasTuple(const std::string& tuple_str) const {
 }
 
 uint16_t
-OptionOpaqueDataTuples::len() {
+OptionOpaqueDataTuples::len() const {
     // The option starts with the header.
     uint16_t length = getHeaderLen();
     // Now iterate over existing tuples and add their size.
@@ -120,7 +125,7 @@ OptionOpaqueDataTuples::len() {
 }
 
 std::string
-OptionOpaqueDataTuples::toText(int indent) {
+OptionOpaqueDataTuples::toText(int indent) const {
     std::ostringstream s;
 
     // Apply indentation

+ 9 - 7
src/lib/dhcp/option_opaque_data_tuples.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -62,10 +62,13 @@ public:
                            OptionBufferConstIter begin,
                            OptionBufferConstIter end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    OptionPtr clone() const;
+
     /// @brief Renders option into the buffer in the wire format.
     ///
     /// @param [out] buf Buffer to which the option is rendered.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses buffer holding an option.
     ///
@@ -123,13 +126,13 @@ public:
     bool hasTuple(const std::string& tuple_str) const;
 
     /// @brief Returns the full length of the option, including option header.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns text representation of the option.
     ///
     /// @param indent Number of space characters before text.
     /// @return Text representation of the option.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
 private:
 
@@ -137,12 +140,11 @@ private:
     ///
     /// This function returns the length field type which should be used
     /// for the opaque data tuples being added to this option.
-    /// Currently this class is only used for a DHCPv6 option it may be expanded
-    /// for DHCPv4 in the future.
     ///
     /// @return Tuple length field type for the universe this option belongs to.
     OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
-        return (OpaqueDataTuple::LENGTH_2_BYTES);
+        return (universe_ == Option::V6 ? OpaqueDataTuple::LENGTH_2_BYTES :
+                OpaqueDataTuple::LENGTH_1_BYTE);
     }
 
     /// @brief Returns minimal length of the option for the given universe.

+ 10 - 5
src/lib/dhcp/option_string.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -27,6 +27,11 @@ OptionString::OptionString(const Option::Universe u, const uint16_t type,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionString::clone() const {
+    return (cloneInternal<OptionString>());
+}
+
 std::string
 OptionString::getValue() const {
     const OptionBuffer& data = getData();
@@ -48,12 +53,12 @@ OptionString::setValue(const std::string& value) {
 
 
 uint16_t
-OptionString::len() {
+OptionString::len() const {
     return (getHeaderLen() + getData().size());
 }
 
 void
-OptionString::pack(isc::util::OutputBuffer& buf) {
+OptionString::pack(isc::util::OutputBuffer& buf) const {
     // Pack option header.
     packHeader(buf);
     // Pack data.
@@ -76,7 +81,7 @@ OptionString::unpack(OptionBufferConstIter begin,
 }
 
 std::string
-OptionString::toText(int indent) {
+OptionString::toText(int indent) const {
     std::ostringstream output;
     output << headerToText(indent) << ": "
            << "\"" << getValue() << "\" (string)";
@@ -85,7 +90,7 @@ OptionString::toText(int indent) {
 }
 
 std::string
-OptionString::toString() {
+OptionString::toString() const {
     return (getValue());
 }
 

+ 8 - 5
src/lib/dhcp/option_string.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -56,10 +56,13 @@ public:
     OptionString(const Option::Universe u, const uint16_t type,
                  OptionBufferConstIter begin, OptionBufferConstIter end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    OptionPtr clone() const;
+
     /// @brief Returns length of the whole option, including header.
     ///
     /// @return length of the whole option.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns the string value held by the option.
     ///
@@ -80,7 +83,7 @@ public:
     /// is moved to the end of stored data.
     ///
     /// @param [out] buf output buffer where the option will be stored.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Decodes option data from the provided buffer.
     ///
@@ -101,13 +104,13 @@ public:
     /// the text.
     ///
     /// @return Option information in the textual format.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
     /// @brief Returns actual value of the option in string format.
     ///
     /// This method is used in client classification.
     /// @return Content of the option.
-    virtual std::string toString();
+    virtual std::string toString() const;
 };
 
 /// Pointer to the OptionString object.

+ 10 - 6
src/lib/dhcp/option_vendor.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -23,8 +23,12 @@ OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionVendor::clone() const {
+    return (cloneInternal<OptionVendor>());
+}
 
-void OptionVendor::pack(isc::util::OutputBuffer& buf) {
+void OptionVendor::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);
 
     // Store vendor-id
@@ -59,7 +63,7 @@ void OptionVendor::unpack(OptionBufferConstIter begin,
     }
 }
 
-uint16_t OptionVendor::len() {
+uint16_t OptionVendor::len() const {
     uint16_t length = getHeaderLen();
 
     length += sizeof(uint32_t); // Vendor-id field
@@ -70,7 +74,7 @@ uint16_t OptionVendor::len() {
     }
 
     // length of all suboptions
-    for (OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -79,7 +83,7 @@ uint16_t OptionVendor::len() {
 }
 
 uint8_t
-OptionVendor::dataLen() {
+OptionVendor::dataLen() const {
     // Calculate and store data-len as follows:
     // data-len = total option length - header length
     //            - enterprise id field length - data-len field size
@@ -87,7 +91,7 @@ OptionVendor::dataLen() {
 }
 
 std::string
-OptionVendor::toText(int indent) {
+OptionVendor::toText(int indent) const {
     std::stringstream output;
     output << headerToText(indent) << ": "
            << getVendorId() << " (uint32)";

+ 8 - 5
src/lib/dhcp/option_vendor.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -50,11 +50,14 @@ public:
     OptionVendor(Option::Universe u, OptionBufferConstIter begin,
                  OptionBufferConstIter end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    OptionPtr clone() const;
+
     /// @brief Writes option in wire-format to buf, returns pointer to first
     /// unused byte after stored option.
     ///
     /// @param [out] buf buffer (option will be stored here)
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer
     ///
@@ -82,14 +85,14 @@ public:
     /// Returns length of this option, including option header and suboptions
     ///
     /// @return length of this option
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns the option in the textual format.
     ///
     /// @param indent Number of spaces to be inserted before the text.
     ///
     /// @return Vendor option in the textual format.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
 private:
 
@@ -101,7 +104,7 @@ private:
     /// this value.
     ///
     /// @return Returns calculated data-len value.
-    uint8_t dataLen();
+    uint8_t dataLen() const;
 
     uint32_t vendor_id_;  ///< Enterprise-id
 };

+ 12 - 7
src/lib/dhcp/option_vendor_class.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -20,15 +20,20 @@ OptionVendorClass::OptionVendorClass(Option::Universe u,
     }
 }
 
-    OptionVendorClass::OptionVendorClass(Option::Universe u,
-                                         OptionBufferConstIter begin,
-                                         OptionBufferConstIter end)
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                     OptionBufferConstIter begin,
+                                     OptionBufferConstIter end)
     : Option(u, getOptionCode(u)) {
     unpack(begin, end);
 }
 
+OptionPtr
+OptionVendorClass::clone() const {
+    return (cloneInternal<OptionVendorClass>());
+}
+
 void
-OptionVendorClass::pack(isc::util::OutputBuffer& buf) {
+OptionVendorClass::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);
 
     buf.writeUint32(getVendorId());
@@ -138,7 +143,7 @@ OptionVendorClass::hasTuple(const std::string& tuple_str) const {
 
 
 uint16_t
-OptionVendorClass::len() {
+OptionVendorClass::len() const {
     // The option starts with the header and enterprise id.
     uint16_t length = getHeaderLen() + sizeof(uint32_t);
     // Now iterate over existing tuples and add their size.
@@ -157,7 +162,7 @@ OptionVendorClass::len() {
 }
 
 std::string
-OptionVendorClass::toText(int indent) {
+OptionVendorClass::toText(int indent) const {
     std::ostringstream s;
 
     // Apply indentation

+ 7 - 4
src/lib/dhcp/option_vendor_class.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -69,10 +69,13 @@ public:
     OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
                       OptionBufferConstIter end);
 
+    /// @brief Copies this option and returns a pointer to the copy.
+    OptionPtr clone() const;
+
     /// @brief Renders option into the buffer in the wire format.
     ///
     /// @param [out] buf Buffer to which the option is rendered.
-    virtual void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses buffer holding an option.
     ///
@@ -135,13 +138,13 @@ public:
     bool hasTuple(const std::string& tuple_str) const;
 
     /// @brief Returns the full length of the option, including option header.
-    virtual uint16_t len();
+    virtual uint16_t len() const;
 
     /// @brief Returns text representation of the option.
     ///
     /// @param indent Number of space characters before text.
     /// @return Text representation of the option.
-    virtual std::string toText(int indent = 0);
+    virtual std::string toText(int indent = 0) const;
 
 private:
 

+ 20 - 5
src/lib/dhcp/pkt.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -24,7 +24,8 @@ Pkt::Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr,
      remote_addr_(remote_addr),
      local_port_(local_port),
      remote_port_(remote_port),
-     buffer_out_(0)
+     buffer_out_(0),
+     copy_retrieved_options_(false)
 {
 }
 
@@ -38,7 +39,8 @@ Pkt::Pkt(const uint8_t* buf, uint32_t len, const isc::asiolink::IOAddress& local
      remote_addr_(remote_addr),
      local_port_(local_port),
      remote_port_(remote_port),
-     buffer_out_(0)
+     buffer_out_(0),
+     copy_retrieved_options_(false)
 {
 
     if (len != 0) {
@@ -56,10 +58,23 @@ Pkt::addOption(const OptionPtr& opt) {
 }
 
 OptionPtr
-Pkt::getOption(uint16_t type) const {
+Pkt::getNonCopiedOption(const uint16_t type) const {
     OptionCollection::const_iterator x = options_.find(type);
     if (x != options_.end()) {
-        return (*x).second;
+        return (x->second);
+    }
+    return (OptionPtr());
+}
+
+OptionPtr
+Pkt::getOption(const uint16_t type) {
+    OptionCollection::iterator x = options_.find(type);
+    if (x != options_.end()) {
+        if (copy_retrieved_options_) {
+            OptionPtr option_copy = x->second->clone();
+            x->second = option_copy;
+        }
+        return (x->second);
     }
     return (OptionPtr()); // NULL
 }

+ 128 - 10
src/lib/dhcp/pkt.h

@@ -14,11 +14,69 @@
 #include <dhcp/classify.h>
 
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <utility>
 
 namespace isc {
 
 namespace dhcp {
 
+/// @brief RAII object enabling copying options retrieved from the
+/// packet.
+///
+/// This object enables copying retrieved options from a packet within
+/// a scope in which this object exists. When the object goes out of scope
+/// copying options is disabled. This is applicable in cases when the
+/// server is going to invoke a callout (hook library) where copying options
+/// must be enabled by default. When the callouts return copying options
+/// should be disabled. The use of RAII object eliminates the need for
+/// explicitly re-disabling options copying and is safer in case of
+/// exceptions thrown by callouts and a presence of multiple exit points.
+///
+/// @tparam PktType Type of the packet, e.g. Pkt4, Pkt6, Pkt4o6.
+template<typename PktType>
+class ScopedEnableOptionsCopy {
+public:
+
+    /// @brief Pointer to an encapsulated packet.
+    typedef boost::shared_ptr<PktType> PktTypePtr;
+
+    /// @brief Constructor.
+    ///
+    /// Enables options copying on a packet(s).
+    ///
+    /// @param pkt1 Pointer to first packet.
+    /// @param pkt2 Optional pointer to the second packet.
+    ScopedEnableOptionsCopy(const PktTypePtr& pkt1,
+                            const PktTypePtr& pkt2 = PktTypePtr())
+        : pkts_(pkt1, pkt2) {
+        if (pkt1) {
+            pkt1->setCopyRetrievedOptions(true);
+        }
+        if (pkt2) {
+            pkt2->setCopyRetrievedOptions(true);
+        }
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Disables options copying on a packets.
+    ~ScopedEnableOptionsCopy() {
+        if (pkts_.first) {
+            pkts_.first->setCopyRetrievedOptions(false);
+        }
+        if (pkts_.second) {
+            pkts_.second->setCopyRetrievedOptions(false);
+        }
+    }
+
+private:
+
+    /// @brief Holds a pointers to the packets.
+    std::pair<PktTypePtr, PktTypePtr> pkts_;
+};
+
 /// @brief Base class for classes representing DHCP messages.
 ///
 /// This is a base class that holds common information (e.g. source
@@ -74,7 +132,7 @@ public:
     /// prior to calling this method.
     ///
     /// Output buffer will be stored in buffer_out_.
-    /// The buffer_out_ should be cleared before writting to the buffer
+    /// The buffer_out_ should be cleared before writing to the buffer
     /// in the derived classes.
     ///
     /// @note This is a pure virtual method and must be implemented in
@@ -190,7 +248,7 @@ public:
     /// For all unsupported messages the derived classes must return
     /// "UNKNOWN".
     ///
-    /// @return Ponter to "const" string containing DHCP message name.
+    /// @return Pointer to "const" string containing DHCP message name.
     /// The implementations in the derived classes should statically
     /// allocate returned strings and the caller must not release the
     /// returned pointer.
@@ -220,7 +278,7 @@ public:
     ///
     /// @note It is a matter of naming convention. Conceptually, the server
     /// processes a stream of packets, with some packets belonging to given
-    /// classes. From that perspective, this method adds a packet to specifed
+    /// classes. From that perspective, this method adds a packet to specified
     /// class. Implementation wise, it looks the opposite - the class name
     /// is added to the packet. Perhaps the most appropriate name for this
     /// method would be associateWithClass()? But that seems overly long,
@@ -239,24 +297,78 @@ public:
     ///
     /// @warning This public member is accessed by derived
     /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
     OptionBuffer data_;
 
+protected:
+
+    /// @brief Returns the first option of specified type without copying.
+    ///
+    /// This method is internally used by the @ref Pkt class and derived
+    /// classes to retrieve a pointer to the specified option. This
+    /// method doesn't copy the option before returning it to the
+    /// caller.
+    ///
+    /// @param type Option type.
+    ///
+    /// @return Pointer to the option of specified type or NULL pointer
+    /// if such option is not present.
+    OptionPtr getNonCopiedOption(const uint16_t type) const;
+
+public:
+
     /// @brief Returns the first option of specified type.
     ///
     /// Returns the first option of specified type. Note that in DHCPv6 several
     /// instances of the same option are allowed (and frequently used).
-    /// Also see \ref Pkt6::getOptions().
+    /// Also see @ref Pkt6::getOptions().
     ///
     /// The options will be only returned after unpack() is called.
     ///
     /// @param type option type we are looking for
     ///
     /// @return pointer to found option (or NULL)
-    OptionPtr getOption(uint16_t type) const;
+    OptionPtr getOption(const uint16_t type);
+
+    /// @brief Controls whether the option retrieved by the @ref Pkt::getOption
+    /// should be copied before being returned.
+    ///
+    /// Setting this value to true enables the mechanism of copying options
+    /// retrieved from the packet to prevent accidental modifications of
+    /// options that shouldn't be modified. The typical use case for this
+    /// mechanism is to prevent hook library from modifying instance of
+    /// an option within the packet that would also affect the value for
+    /// this option within the Kea configuration structures.
+    ///
+    /// Kea doesn't copy option instances which it stores in the packet.
+    /// It merely copy pointers into the packets. Thus, any modification
+    /// to an option would change the value of this option in the
+    /// Kea configuration. To prevent this, option copying should be
+    /// enabled prior to passing the pointer to a packet to a hook library.
+    ///
+    /// Note that only only does this method causes the server to copy
+    /// an option, but the copied option also replaces the original
+    /// option within the packet. The option can be then freely modified
+    /// and the modifications will only affect the instance of this
+    /// option within the packet but not within the server configuration.
+    ///
+    /// @param copy Indicates if the options should be copied when
+    /// retrieved (if true), or not copied (if false).
+    virtual void setCopyRetrievedOptions(const bool copy) {
+        copy_retrieved_options_ = copy;
+    }
+
+    /// @brief Returns whether the copying of retrieved options is enabled.
+    ///
+    /// Also see @ref setCopyRetrievedOptions.
+    ///
+    /// @return true if retrieved options are copied.
+    bool isCopyRetrievedOptions() const {
+        return (copy_retrieved_options_);
+    }
 
     /// @brief Update packet timestamp.
     ///
@@ -439,7 +551,7 @@ public:
     /// @param hw_addr_src a bitmask that specifies hardware address source
     HWAddrPtr getMAC(uint32_t hw_addr_src);
 
-    /// @brief Virtual desctructor.
+    /// @brief Virtual destructor.
     ///
     /// There is nothing to clean up here, but since there are virtual methods,
     /// we define virtual destructor to ensure that derived classes will have
@@ -459,7 +571,7 @@ public:
     ///
     /// @warning This public member is accessed by derived
     /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
@@ -609,12 +721,18 @@ protected:
     ///
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
     isc::util::OutputBuffer buffer_out_;
 
+    /// @brief Indicates if a copy of the retrieved option should be
+    /// returned when @ref Pkt::getOption is called.
+    ///
+    /// @see the documentation for @ref Pkt::setCopyRetrievedOptions.
+    bool copy_retrieved_options_;
+
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
 
@@ -633,7 +751,7 @@ private:
     /// @param hw_addr pointer to actual hardware address.
     /// @param [out] storage pointer to a class member to be modified.
     ///
-    /// @trow isc::OutOfRange if invalid HW address specified.
+    /// @throw isc::OutOfRange if invalid HW address specified.
     virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
                                  const std::vector<uint8_t>& hw_addr,
                                  HWAddrPtr& storage);

+ 4 - 4
src/lib/dhcp/pkt4.cc

@@ -228,7 +228,7 @@ Pkt4::unpack() {
 }
 
 uint8_t Pkt4::getType() const {
-    OptionPtr generic = getOption(DHO_DHCP_MESSAGE_TYPE);
+    OptionPtr generic = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
     if (!generic) {
         return (DHCP_NOTYPE);
     }
@@ -245,7 +245,7 @@ uint8_t Pkt4::getType() const {
 }
 
 void Pkt4::setType(uint8_t dhcp_type) {
-    OptionPtr opt = getOption(DHO_DHCP_MESSAGE_TYPE);
+    OptionPtr opt = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
     if (opt) {
 
         // There is message type option already, update it. It seems that
@@ -332,7 +332,7 @@ Pkt4::getLabel() const {
     /// use the instance member rather than fetch it every time.
     std::string suffix;
     ClientIdPtr client_id;
-    OptionPtr client_opt = getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    OptionPtr client_opt = getNonCopiedOption(DHO_DHCP_CLIENT_IDENTIFIER);
     if (client_opt) {
         try {
             client_id = ClientIdPtr(new ClientId(client_opt->getData()));
@@ -546,7 +546,7 @@ Pkt4::getHlen() const {
 void
 Pkt4::addOption(const OptionPtr& opt) {
     // Check for uniqueness (DHCPv4 options must be unique)
-    if (getOption(opt->getType())) {
+    if (getNonCopiedOption(opt->getType())) {
         isc_throw(BadValue, "Option " << opt->getType()
                   << " already present in this message.");
     }

+ 9 - 1
src/lib/dhcp/pkt4o6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -47,6 +47,14 @@ void Pkt4o6::pack() {
     pkt6_->pack();
 }
 
+void
+Pkt4o6::setCopyRetrievedOptions(const bool copy) {
+    Pkt4::setCopyRetrievedOptions(copy);
+    // Copy the new setting to the encapsulated instance of Pkt6.
+    pkt6_->setCopyRetrievedOptions(copy);
+}
+
+
 } // end of namespace isc::dhcp
 
 } // end of namespace isc

+ 12 - 1
src/lib/dhcp/pkt4o6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -59,6 +59,17 @@ public:
         return (true);
     }
 
+    /// @brief Overrides the @ref Pkt::setCopyRetrievedOptions to also
+    /// set the flag for encapsulated @ref Pkt6 instance.
+    ///
+    /// When the flag is set for the instance of the @ref Pkt4o6 the
+    /// encapsulated Pkt6, retrieved with @ref Pkt4o6::getPkt6, will
+    /// inherit this setting.
+    ///
+    /// @param copy Indicates if the options should be copied when
+    /// retrieved (if true), or not copied (if false).
+    virtual void setCopyRetrievedOptions(const bool copy);
+
 private:
     /// Encapsulating DHCPv6 message
     Pkt6Ptr pkt6_;

+ 110 - 29
src/lib/dhcp/pkt6.cc

@@ -18,6 +18,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/iface_mgr.h>
 
+#include <iterator>
 #include <iostream>
 #include <sstream>
 
@@ -57,17 +58,9 @@ size_t Pkt6::len() {
     }
 }
 
-OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
-
-    if (relay_info_.empty()) {
-        // There's no relay info, this is a direct message
-        return (OptionPtr());
-    }
-
-    int start = 0; // First relay to check
-    int end = 0;   // Last relay to check
-    int direction = 0; // How we going to iterate: forward or backward?
-
+void
+Pkt6::prepareGetAnyRelayOption(const RelaySearchOrder& order,
+                               int& start, int& end, int& direction) const {
     switch (order) {
     case RELAY_SEARCH_FROM_CLIENT:
         // Search backwards
@@ -93,6 +86,55 @@ OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
         end = 0;
         direction = 1;
     }
+}
+
+
+OptionPtr
+Pkt6::getNonCopiedAnyRelayOption(const uint16_t option_code,
+                                 const RelaySearchOrder& order) const {
+    if (relay_info_.empty()) {
+        // There's no relay info, this is a direct message
+        return (OptionPtr());
+    }
+
+    int start = 0; // First relay to check
+    int end = 0;   // Last relay to check
+    int direction = 0; // How we going to iterate: forward or backward?
+
+    prepareGetAnyRelayOption(order, start, end, direction);
+
+    // This is a tricky loop. It must go from start to end, but it must work in
+    // both directions (start > end; or start < end). We can't use regular
+    // exit condition, because we don't know whether to use i <= end or i >= end.
+    // That's why we check if in the next iteration we would go past the
+    // list (end + direction). It is similar to STL concept of end pointing
+    // to a place after the last element
+    for (int i = start; i != end + direction; i += direction) {
+        OptionPtr opt = getNonCopiedRelayOption(option_code, i);
+        if (opt) {
+            return (opt);
+        }
+    }
+
+    // We iterated over specified relays and haven't found what we were
+    // looking for
+    return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getAnyRelayOption(const uint16_t option_code,
+                        const RelaySearchOrder& order) {
+
+    if (relay_info_.empty()) {
+        // There's no relay info, this is a direct message
+        return (OptionPtr());
+    }
+
+    int start = 0; // First relay to check
+    int end = 0;   // Last relay to check
+    int direction = 0; // How we going to iterate: forward or backward?
+
+    prepareGetAnyRelayOption(order, start, end, direction);
 
     // This is a tricky loop. It must go from start to end, but it must work in
     // both directions (start > end; or start < end). We can't use regular
@@ -101,7 +143,7 @@ OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
     // list (end + direction). It is similar to STL concept of end pointing
     // to a place after the last element
     for (int i = start; i != end + direction; i += direction) {
-        OptionPtr opt = getRelayOption(opt_type, i);
+        OptionPtr opt = getRelayOption(option_code, i);
         if (opt) {
             return (opt);
         }
@@ -112,16 +154,41 @@ OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
     return (OptionPtr());
 }
 
-OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) const {
+OptionPtr
+Pkt6::getNonCopiedRelayOption(const uint16_t opt_type,
+                              const uint8_t relay_level) const {
     if (relay_level >= relay_info_.size()) {
-        isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
-                  << " There is no info about " << relay_level + 1 << " relay.");
+        isc_throw(OutOfRange, "This message was relayed "
+                  << relay_info_.size() << " time(s)."
+                  << " There is no info about "
+                  << relay_level + 1 << " relay.");
     }
 
     OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type);
     if (x != relay_info_[relay_level].options_.end()) {
-	return (*x).second;
-      }
+        return (x->second);
+    }
+
+    return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) {
+    if (relay_level >= relay_info_.size()) {
+        isc_throw(OutOfRange, "This message was relayed "
+                  << relay_info_.size() << " time(s)."
+                  << " There is no info about "
+                  << relay_level + 1 << " relay.");
+    }
+
+    OptionCollection::iterator x = relay_info_[relay_level].options_.find(opt_type);
+    if (x != relay_info_[relay_level].options_.end()) {
+        if (copy_retrieved_options_) {
+            OptionPtr relay_option_copy = x->second->clone();
+            x->second = relay_option_copy;
+        }
+        return (x->second);
+    }
 
     return (OptionPtr());
 }
@@ -451,7 +518,7 @@ Pkt6::unpackTCP() {
 HWAddrPtr
 Pkt6::getMACFromDUID() {
     HWAddrPtr mac;
-    OptionPtr opt_duid = getOption(D6O_CLIENTID);
+    OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
     if (!opt_duid) {
         return (mac);
     }
@@ -554,7 +621,7 @@ Pkt6::toText() const {
 
 DuidPtr
 Pkt6::getClientId() const {
-    OptionPtr opt_duid = getOption(D6O_CLIENTID);
+    OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
     try {
         // This will throw if the DUID length is larger than 128 bytes
         // or is too short.
@@ -570,16 +637,30 @@ Pkt6::getClientId() const {
 }
 
 isc::dhcp::OptionCollection
-Pkt6::getOptions(uint16_t opt_type) {
-    isc::dhcp::OptionCollection found;
+Pkt6::getNonCopiedOptions(const uint16_t opt_type) const {
+    std::pair<OptionCollection::const_iterator,
+              OptionCollection::const_iterator> range = options_.equal_range(opt_type);
+    return (OptionCollection(range.first, range.second));
+}
 
-    for (OptionCollection::const_iterator x = options_.begin();
-         x != options_.end(); ++x) {
-        if (x->first == opt_type) {
-            found.insert(make_pair(opt_type, x->second));
+isc::dhcp::OptionCollection
+Pkt6::getOptions(const uint16_t opt_type) {
+    OptionCollection options_copy;
+
+    std::pair<OptionCollection::iterator,
+              OptionCollection::iterator> range = options_.equal_range(opt_type);
+    // If options should be copied on retrieval, we should now iterate over
+    // matching options, copy them and replace the original ones with new
+    // instances.
+    if (copy_retrieved_options_) {
+        for (OptionCollection::iterator opt_it = range.first;
+             opt_it != range.second; ++opt_it) {
+            OptionPtr option_copy = opt_it->second->clone();
+            opt_it->second = option_copy;
         }
     }
-    return (found);
+    // Finally, return updated options. This can also be empty in some cases.
+    return (OptionCollection(range.first, range.second));
 }
 
 const char*
@@ -668,7 +749,7 @@ const char* Pkt6::getName() const {
 void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
 
     // We use index rather than iterator, because we need that as a parameter
-    // passed to getRelayOption()
+    // passed to getNonCopiedRelayOption()
     for (size_t i = 0; i < question->relay_info_.size(); ++i) {
         RelayInfo info;
         info.msg_type_ = DHCPV6_RELAY_REPL;
@@ -678,7 +759,7 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
 
         // Is there an interface-id option in this nesting level?
         // If there is, we need to echo it back
-        OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
+        OptionPtr opt = question->getNonCopiedRelayOption(D6O_INTERFACE_ID, i);
         // taken from question->RelayInfo_[i].options_
         if (opt) {
             info.options_.insert(make_pair(opt->getType(), opt));
@@ -735,7 +816,7 @@ HWAddrPtr
 Pkt6::getMACFromDocsisModem() {
     HWAddrPtr mac;
     OptionVendorPtr vendor = boost::dynamic_pointer_cast<
-        OptionVendor>(getOption(D6O_VENDOR_OPTS));
+        OptionVendor>(getNonCopiedOption(D6O_VENDOR_OPTS));
 
     // Check if this is indeed DOCSIS3 environment
     if (vendor && vendor->getVendorId() == VENDOR_ID_CABLE_LABS) {

+ 85 - 6
src/lib/dhcp/pkt6.h

@@ -58,7 +58,7 @@ public:
     /// @brief defines relay search pattern
     ///
     /// Defines order in which options are searched in a message that
-    /// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
+    /// passed through multiple relays. RELAY_SEACH_FROM_CLIENT will
     /// start search from the relay that was the closest to the client
     /// (i.e. innermost in the encapsulated message, which also means
     /// this was the first relay that forwarded packet received by the
@@ -224,7 +224,28 @@ public:
     /// @return Pointer to the DUID or NULL if the option doesn't exist.
     DuidPtr getClientId() const;
 
-    /// @brief returns option inserted by relay
+
+protected:
+
+    /// @brief Returns pointer to an option inserted by relay agent.
+    ///
+    /// This is a variant of the @ref Pkt6::getRelayOption function which
+    /// never copies an option returned. This method should be only used by
+    /// the @ref Pkt6 class and derived classes. Any external callers should
+    /// use @ref getRelayOption which copies the option before returning it
+    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+    ///
+    /// @param opt_type Code of the requested option.
+    /// @param relay_level Nesting level as described for
+    /// @ref Pkt6::getRelayOption.
+    ///
+    /// @return Pointer to the option or NULL if such option doesn't exist.
+    OptionPtr getNonCopiedRelayOption(const uint16_t opt_type,
+                                      const uint8_t relay_level) const;
+
+public:
+
+    /// @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
@@ -239,7 +260,47 @@ public:
     /// @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) const;
+    OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
+
+private:
+
+    /// @brief Prepares parameters for loop used in @ref getAnyRelayOption
+    /// and @ref getNonCopiedAnyRelayOption.
+    ///
+    /// The methods retrieving "any" relay option iterate over the relay
+    /// info structures to find the matching option. This method returns
+    /// the index of the first and last relay info structure to be used
+    /// for this iteration. It also returns the direction in which the
+    /// iteration should be performed.
+    ///
+    /// @param order Option search order (see @ref RelaySearchOrder).
+    /// @param [out] start Index of the relay information structure from
+    /// which the search should be started.
+    /// @param [out] end Index of the relay information structure on which
+    /// the option searches should stop.
+    /// @param [out] direction Equals to -1 for backwards searches, and
+    /// equals to 1 for forward searches.
+    void prepareGetAnyRelayOption(const RelaySearchOrder& order,
+                                  int& start, int& end, int& direction) const;
+
+protected:
+
+    /// @brief Returns pointer to an instance of specified option.
+    ///
+    /// This is a variant of @ref getAnyRelayOption but it never copies
+    /// an option returned. This method should be only used by
+    /// the @ref Pkt6 class and derived classes. Any external callers should
+    /// use @ref getAnyRelayOption which copies the option before returning it
+    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+    ///
+    /// @param option_code Searched option.
+    /// @param order Option search order (see @ref RelaySearchOrder).
+    ///
+    /// @return Option pointer or NULL, if no option matches specified criteria.
+    OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code,
+                                         const RelaySearchOrder& order) const;
+
+public:
 
     /// @brief Return first instance of a specified option
     ///
@@ -251,7 +312,8 @@ public:
     /// @param option_code searched option
     /// @param order option search order (see @ref RelaySearchOrder)
     /// @return option pointer (or NULL if no option matches specified criteria)
-    OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
+    OptionPtr getAnyRelayOption(const uint16_t option_code,
+                                const RelaySearchOrder& order);
 
     /// @brief return the link address field from a relay option
     ///
@@ -285,7 +347,24 @@ public:
     const isc::asiolink::IOAddress&
     getRelay6PeerAddress(uint8_t relay_level) const;
 
+protected:
+
+    /// @brief Returns all option instances of specified type without
+    /// copying.
+    ///
+    /// This is a variant of @ref getOptions method, which returns a collection
+    /// of options without copying them. This method should be only used by
+    /// the @ref Pkt6 class and derived classes. Any external callers should
+    /// use @ref getOptions which copies option instances before returning them
+    /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
     ///
+    /// @param opt_type Option code.
+    ///
+    /// @return Collection of options found.
+    OptionCollection getNonCopiedOptions(const uint16_t opt_type) const;
+
+public:
+
     /// @brief Returns all instances of specified type.
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -293,7 +372,7 @@ public:
     ///
     /// @param type option type we are looking for
     /// @return instance of option collection with requested options
-    isc::dhcp::OptionCollection getOptions(uint16_t type);
+    isc::dhcp::OptionCollection getOptions(const uint16_t type);
 
     /// @brief add information about one traversed relay
     ///
@@ -322,7 +401,7 @@ public:
     /// requires one parameter (type).
     ///
     /// @return Pointer to "const" string containing the message name. If
-    /// the message type is unknnown the "UNKNOWN" is returned. The caller
+    /// the message type is unknown the "UNKNOWN" is returned. The caller
     /// must not release the returned pointer.
     const char* getName() const;
 

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -59,6 +59,7 @@ libdhcp___unittests_SOURCES += option_int_unittest.cc
 libdhcp___unittests_SOURCES += option_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_copy_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc

+ 1 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -36,6 +36,7 @@
 
 #include <iostream>
 #include <sstream>
+#include <typeinfo>
 
 #include <arpa/inet.h>
 

+ 790 - 0
src/lib/dhcp/tests/option_copy_unittest.cc

@@ -0,0 +1,790 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_status_code.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Type of the "copy" operation to be performed in a test.
+///
+/// Possible operations are:
+/// - copy construction,
+/// - cloning with Option::clone,
+/// - assignment.
+enum OpType {
+    COPY,
+    CLONE,
+    ASSIGN
+};
+
+/// @brief Generic test for deep copy of an option.
+///
+/// This test can use one of the three supported operations to deep copy
+/// an option: copy construction, cloning or assignment.
+///
+/// After copying the option the following parameters checked if they
+/// have been copied (copied by the Option class):
+/// - universe,
+/// - option type,
+/// - encapsulated space,
+/// - data.
+///
+/// This test also checks that the sub options have been copied by checking
+/// that:
+/// - options' types match,
+/// - binary representations are equal,
+/// - pointers to the options are unequal (to make sure that the option has
+///   been copied, rather than the pointer).
+///
+/// @param op_type Copy operation to be performed.
+/// @param option Source option.
+/// @param option_copy Destination option. Note that this option may be
+/// initially set to a non-null value. For the "copy" and "clone" operations
+/// the pointer will be reset, so there is no sense to initialize this
+/// object to a non-null value. However, for the assignment testing it is
+/// recommended to initialize the option_copy to point to an option having
+/// different parameters to verify that all parameters have been overridden
+/// by the assignment operation.
+template<typename OptionType>
+void testCopyAssign(const OpType& op_type,
+                    boost::shared_ptr<OptionType>& option,
+                    boost::shared_ptr<OptionType>& option_copy) {
+    // Set the encapsulated to 'foo' because tests usually don't set that
+    // value.
+    option->setEncapsulatedSpace("foo");
+
+    // Create two sub options of different types to later check that they
+    // are copied.
+    OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234));
+    Option4AddrLstPtr sub2 =
+        Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3")));
+    option->addOption(sub1);
+    option->addOption(sub2);
+
+    // Copy option by copy construction, cloning or assignment.
+    switch (op_type) {
+    case COPY:
+        option_copy.reset(new OptionType(*option));
+        break;
+    case CLONE:
+        option_copy = boost::dynamic_pointer_cast<OptionType>(option->clone());
+        ASSERT_TRUE(option_copy);
+        break;
+    case ASSIGN:
+        option_copy->setEncapsulatedSpace("bar");
+        *option_copy = *option;
+        break;
+    default:
+        ADD_FAILURE() << "unsupported operation";
+        return;
+    }
+
+    // Verify that basic parameters have been copied.
+    EXPECT_EQ(option->getUniverse(), option_copy->getUniverse());
+    EXPECT_EQ(option->getType(), option_copy->getType());
+    EXPECT_EQ(option->len(), option_copy->len());
+    EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace());
+    EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(),
+                           option_copy->getData().begin()));
+
+    // Retrieve sub options so as they can be compared.
+    const OptionCollection& option_subs = option->getOptions();
+    const OptionCollection& option_copy_subs = option_copy->getOptions();
+    ASSERT_EQ(option_subs.size(), option_copy_subs.size());
+
+    // Iterate over source options.
+    OptionCollection::const_iterator it_copy = option_copy_subs.begin();
+    for (OptionCollection::const_iterator it = option_subs.begin();
+         it != option_subs.end(); ++it, ++it_copy) {
+        // The option codes should be equal in both containers.
+        EXPECT_EQ(it->first, it_copy->first);
+        // Pointers must be unequal because the expectation is that options
+        // are copied, rather than pointers.
+        EXPECT_NE(it->second, it_copy->second);
+        Option* opt_ptr = it->second.get();
+        Option* opt_copy_ptr = it_copy->second.get();
+        // The C++ types must match.
+        EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr));
+    }
+
+    // Final check is to compare their binary representations.
+    std::vector<uint8_t> buf = option->toBinary(true);
+    std::vector<uint8_t> buf_copy = option_copy->toBinary(true);
+
+    ASSERT_EQ(buf.size(), buf_copy.size());
+    EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin()));
+}
+
+// **************************** Option ***************************
+
+/// @brief Test deep copy of option encapsulated by Option type.
+///
+/// @param op_type Copy operation type.
+void testOption(const OpType& op_type) {
+    OptionBuffer buf(10, 1);
+    OptionPtr option(new Option(Option::V4, 1, buf));
+    OptionPtr option_copy(new Option(Option::V6, 1000));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Save binary representation of the original option. We will
+    // be later comparing it with a copied option to make sure that
+    // modification of the original option doesn't affect the copy.
+    std::vector<uint8_t> binary_copy = option_copy->toBinary(true);
+
+    // Modify the original option.
+    OptionBuffer buf_modified(10, 2);
+    option->setData(buf_modified.begin(), buf_modified.end());
+
+    // Retrieve the binary representation of the copy to verify that
+    // it hasn't been modified.
+    std::vector<uint8_t> binary_copy_after = option_copy->toBinary(true);
+
+    ASSERT_EQ(binary_copy.size(), binary_copy_after.size());
+    EXPECT_TRUE(std::equal(binary_copy_after.begin(), binary_copy_after.end(),
+                           binary_copy.begin()));
+}
+
+TEST(OptionCopyTest, optionConstructor) {
+    testOption(COPY);
+}
+
+TEST(OptionCopyTest, optionClone) {
+    testOption(CLONE);
+}
+
+TEST(OptionCopyTest, optionAssignment) {
+    testOption(ASSIGN);
+}
+
+// **************************** OptionInt ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionInt type.
+///
+/// @param op_type Copy operation type.
+void testOptionInt(const OpType& op_type) {
+    OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345));
+    OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify value in the original option.
+    option->setValue(9);
+
+    // The value in the copy should not be affected.
+    EXPECT_EQ(12345, option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionIntConstructor) {
+    testOptionInt(COPY);
+}
+
+TEST(OptionCopyTest, optionIntClone) {
+    testOptionInt(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntAssignment) {
+    testOptionInt(ASSIGN);
+}
+
+// ************************* OptionIntArray ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionIntArray type.
+///
+/// @param op_type Copy operation type.
+void testOptionIntArray(const OpType& op_type) {
+    OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));;
+    option->addValue(2345);
+    option->addValue(3456);
+    OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10));
+    option_copy->addValue(5678);
+    option_copy->addValue(6789);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setValues(std::vector<uint32_t>(2, 7));
+
+    // The values in the copy should not be affected.
+    std::vector<uint32_t> values_copy = option_copy->getValues();
+    ASSERT_EQ(2, values_copy.size());
+    EXPECT_EQ(2345, values_copy[0]);
+    EXPECT_EQ(3456, values_copy[1]);
+}
+
+TEST(OptionCopyTest, optionIntArrayConstructor) {
+    testOptionIntArray(COPY);
+}
+
+TEST(OptionCopyTest, optionIntArrayClone) {
+    testOptionIntArray(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntArrayAssignment) {
+    testOptionIntArray(ASSIGN);
+}
+
+// ************************* Option4AddrLst ***************************
+
+/// @brief Test deep copy of option encapsulated by Option4AddrLst or
+/// Option6AddrLst type.
+///
+/// @param op_type Copy operation type.
+/// @param option_address Address carried in the source option.
+/// @param option_copy_address Address carried in the destination option.
+/// @param option_modified_address Address to which the original address
+/// is modified to check that this modification doesn't affect option
+/// copy.
+/// @tparam OptionType Option4AddrLst or Option6AddrLst.
+template<typename OptionType>
+void testOptionAddrLst(const OpType& op_type,
+                       const IOAddress& option_address,
+                       const IOAddress& option_copy_address,
+                       const IOAddress& option_modified_address) {
+    typedef boost::shared_ptr<OptionType> OptionTypePtr;
+    OptionTypePtr option(new OptionType(1, option_address));
+    OptionTypePtr option_copy(new OptionType(10, option_copy_address));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the address in the original option.
+    option->setAddress(option_modified_address);
+
+    // The address in the copy should not be affected.
+    typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses();
+    ASSERT_EQ(1, addrs_copy.size());
+    EXPECT_EQ(option_address.toText(), addrs_copy[0].toText());
+}
+
+/// @brief Test deep copy of option encapsulated by Option4AddrLst type.
+///
+/// @param op_type Copy operation type.
+void testOption4AddrLst(const OpType& op_type) {
+    testOptionAddrLst<Option4AddrLst>(op_type,
+                                      IOAddress("127.0.0.1"),
+                                      IOAddress("192.0.2.111"),
+                                      IOAddress("127.0.0.1"));
+}
+
+TEST(OptionCopyTest, option4AddrLstConstructor) {
+    testOption4AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option4AddrLstClone) {
+    testOption4AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option4AddrLstAssignment) {
+    testOption4AddrLst(ASSIGN);
+}
+
+// ************************* Option6AddrLst ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6AddrLst type.
+///
+/// @param op_type Copy operation type.
+void testOption6AddrLst(const OpType& op_type) {
+    testOptionAddrLst<Option6AddrLst>(op_type,
+                                      IOAddress("2001:db8:1::2"),
+                                      IOAddress("3001::cafe"),
+                                      IOAddress("3000:1::1"));
+}
+
+TEST(OptionCopyTest, option6AddrLstConstructor) {
+    testOption6AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option6AddrLstClone) {
+    testOption6AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option6AddrLstAssignment) {
+    testOption6AddrLst(ASSIGN);
+}
+
+// *************************** Option6IA ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IA type.
+///
+/// @param op_type Copy operation type.
+void testOption6IA(const OpType& op_type) {
+    Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234));
+    option->setT1(1000);
+    option->setT2(2000);
+    Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setT1(3000);
+    option->setT2(4000);
+    option->setIAID(5678);
+
+    // The values in the copy should not be affected.
+    EXPECT_EQ(1000, option_copy->getT1());
+    EXPECT_EQ(2000, option_copy->getT2());
+    EXPECT_EQ(1234, option_copy->getIAID());
+}
+
+TEST(OptionCopyTest, option6IAConstructor) {
+    testOption6IA(COPY);
+}
+
+TEST(OptionCopyTest, option6IAClone) {
+    testOption6IA(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAssignment) {
+    testOption6IA(ASSIGN);
+}
+
+// *************************** Option6IAAddr ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IAAddr type.
+///
+/// @param op_type Copy operation type.
+void testOption6IAAddr(const OpType& op_type) {
+    Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR,
+                                              IOAddress("2001:db8:1::1"),
+                                              60, 90));
+    Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR,
+                                                       IOAddress("2001:db8:1::2"),
+                                                       50, 80));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setAddress(IOAddress("2001:db8:1::3"));
+    option->setPreferred(1000);
+    option->setValid(2000);
+
+    // The values in the copy should not be affected.
+    EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText());
+    EXPECT_EQ(60, option_copy->getPreferred());
+    EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAAddrConstructor) {
+    testOption6IAAddr(COPY);
+}
+
+TEST(OptionCopyTest, option6IAAddrClone) {
+    testOption6IAAddr(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAddrAssignment) {
+    testOption6IAAddr(ASSIGN);
+}
+
+// *************************** Option6IAPrefix ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IAPrefix type.
+///
+/// @param op_type Copy operation type.
+void testOption6IAPrefix(const OpType& op_type) {
+   Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX,
+                                                  IOAddress("3000::"),
+                                                  64, 60, 90));
+    Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX,
+                                                           IOAddress("3001::"),
+                                                           48, 50, 80));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setPrefix(IOAddress("3002::"), 32);
+    option->setPreferred(1000);
+    option->setValid(2000);
+
+    // The values in the copy should not be affected.
+    EXPECT_EQ("3000::", option_copy->getAddress().toText());
+    EXPECT_EQ(64, option_copy->getLength());
+    EXPECT_EQ(60, option_copy->getPreferred());
+    EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAPrefixConstructor) {
+    testOption6IAPrefix(COPY);
+}
+
+TEST(OptionCopyTest, option6IAPrefixClone) {
+    testOption6IAPrefix(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAPrefixAssignment) {
+    testOption6IAPrefix(ASSIGN);
+}
+
+// *************************** Option6StatusCode ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6StatusCode type.
+///
+/// @param op_type Copy operation type.
+void testOption6StatusCode(const OpType& op_type) {
+    Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding,
+                                                      "no binding"));
+    Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success,
+                                                           "success"));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setStatusCode(STATUS_NoAddrsAvail);
+    option->setStatusMessage("foo");
+
+    // The values in the copy should not be affected.
+    EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode());
+    EXPECT_EQ("no binding", option_copy->getStatusMessage());
+}
+
+TEST(OptionCopyTest, option6StatusCodeConstructor) {
+    testOption6StatusCode(COPY);
+}
+
+TEST(OptionCopyTest, option6StatusCodeClone) {
+    testOption6StatusCode(CLONE);
+}
+
+TEST(OptionCopyTest, option6StatusCodeAssignment) {
+    testOption6StatusCode(ASSIGN);
+}
+
+// *************************** OptionString ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionString type.
+///
+/// @param op_type Copy operation type.
+void testOptionString(const OpType& op_type) {
+    OptionStringPtr option(new OptionString(Option::V4, 1, "option value"));
+    OptionStringPtr option_copy(new OptionString(Option::V6, 10,
+                                                     "another value"));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the string in the original option.
+    option->setValue("foo");
+
+    // The string in the copy should not be affected.
+    EXPECT_EQ("option value", option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionStringConstructor) {
+    testOptionString(COPY);
+}
+
+TEST(OptionCopyTest, optionStringClone) {
+    testOptionString(CLONE);
+}
+
+TEST(OptionCopyTest, optionStringAssignment) {
+    testOptionString(ASSIGN);
+}
+
+// *************************** OptionVendor ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionVendor type.
+///
+/// @param op_type Copy operation type.
+void testOptionVendor(const OpType& op_type) {
+    OptionVendorPtr option(new OptionVendor(Option::V4, 2986));
+    OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the vendor id in the original option.
+    option->setVendorId(2222);
+
+    // The vendor id in the copy should not be affected.
+    EXPECT_EQ(2986, option_copy->getVendorId());
+}
+
+TEST(OptionCopyTest, optionVendorConstructor) {
+    testOptionVendor(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClone) {
+    testOptionVendor(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorAssignment) {
+    testOptionVendor(ASSIGN);
+}
+
+// *********************** OptionVendorClass ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionVendorClass type.
+///
+/// @param op_type Copy operation type.
+void testOptionVendorClass(const OpType& op_type) {
+    // Create a DHCPv4 option with a single tuple.
+    OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986));
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "vendor-class-value";
+    option->setTuple(0, tuple);
+
+    // Create a DHCPv6 option with a single tuple.
+    OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6,
+                                                               1111));
+    OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "vendor-class-assigned";
+    option_copy->addTuple(tuple_copy);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the tuple in the original option and add one more tuple.
+    tuple = "modified-vendor-class-value";
+    option->setTuple(0, tuple);
+    tuple = "another-modified-vendor-class-value";
+    option->addTuple(tuple);
+
+    // That change shouldn't affect the original option. It should still
+    // contain a single tuple with the original value.
+    ASSERT_EQ(1, option_copy->getTuplesNum());
+    tuple = option_copy->getTuple(0);
+    EXPECT_TRUE(tuple.equals("vendor-class-value"));
+}
+
+TEST(OptionCopyTest, optionVendorClassConstructor) {
+    testOptionVendorClass(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClassClone) {
+    testOptionVendorClass(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorClassAssignment) {
+    testOptionVendorClass(ASSIGN);
+}
+
+// ************************** Option4ClientFqdn ***************************
+
+/// @brief Test deep copy of option encapsulated by Option4ClientFqdn or
+/// Option6ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+/// @param option Option to be copied.
+/// @param option_copy Destination option. Note that this option may be
+/// initially set to a non-null value. For the "copy" and "clone" operations
+/// the pointer will be reset, so there is no sense to initialize this
+/// object to a non-null value. However, for the assignment testing it is
+/// recommended to initialize the option_copy to point to an option having
+/// different parameters to verify that all parameters have been overridden
+/// by the assignment operation.
+///
+/// @tparam OptionType Option4ClientFqdn or Option6ClientFqdn.
+template<typename OptionType>
+void testOptionClientFqdn(const OpType& op_type,
+                          boost::shared_ptr<OptionType>& option,
+                          boost::shared_ptr<OptionType>& option_copy) {
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the values in the original option.
+    option->setDomainName("newname", OptionType::PARTIAL);
+    option->setFlag(OptionType::FLAG_S, false);
+    option->setFlag(OptionType::FLAG_N, true);
+
+    // Rcode is carried on the in the DHCPv4 Client FQDN option.
+    // If the OptionType is pointing to a DHCPv6 option the dynamic
+    // cast will result in NULL pointer and we'll not check the
+    // RCODE.
+    Option4ClientFqdnPtr option4 =
+        boost::dynamic_pointer_cast<Option4ClientFqdn>(option);
+    if (option4) {
+        option4->setRcode(64);
+    }
+
+    // Verify that common parameters haven't been modified in the
+    // copied option by the change in the original option.
+    EXPECT_EQ("myname.example.org.", option_copy->getDomainName());
+    EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType());
+    EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N));
+
+    // If we're dealing with DHCPv4 Client FQDN, we also need to
+    // test RCODE.
+    Option4ClientFqdnPtr option_copy4 =
+        boost::dynamic_pointer_cast<Option4ClientFqdn>(option_copy);
+    if (option_copy4) {
+        EXPECT_EQ(255, option_copy4->getRcode().first.getCode());
+        EXPECT_EQ(255, option_copy4->getRcode().second.getCode());
+    }
+}
+
+/// @brief Test deep copy of option encapsulated by Option4ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+void testOption4ClientFqdn(const OpType& op_type) {
+    Option4ClientFqdnPtr
+        option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                     Option4ClientFqdn::Rcode(255),
+                                     "myname.example.org"));
+    Option4ClientFqdnPtr
+        option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                          Option4ClientFqdn::Rcode(0),
+                                          "other.example.org"));
+
+    ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option4ClientFqdn>(op_type, option,
+                                                                    option_copy));
+}
+
+TEST(OptionCopyTest, option4ClientFqdnConstructor) {
+    testOption4ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnClone) {
+    testOption4ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnAssignment) {
+    testOption4ClientFqdn(ASSIGN);
+}
+
+// ************************** Option6ClientFqdn ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+void testOption6ClientFqdn(const OpType& op_type) {
+    Option6ClientFqdnPtr
+        option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                     "myname.example.org"));
+    Option6ClientFqdnPtr
+        option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                          "other.example.org"));
+
+    ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option6ClientFqdn>(op_type, option,
+                                                                    option_copy));
+}
+
+TEST(OptionCopyTest, option6ClientFqdnConstructor) {
+    testOption6ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnClone) {
+    testOption6ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnAssignment) {
+    testOption6ClientFqdn(ASSIGN);
+}
+
+// **************************** OptionCustom ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionCustom type.
+///
+/// @param op_type Copy operation type.
+void testOptionCustom(const OpType& op_type) {
+    // Create option with a single field carrying 16-bits integer.
+    OptionDefinition def("foo", 1, "uint16", true);
+    OptionCustomPtr option(new OptionCustom(def, Option::V4));
+    option->addArrayDataField<uint16_t>(5555);
+
+    // Create option with two fields carrying IPv4 address and 32-bit
+    // integer.
+    OptionDefinition def_copy("bar", 10, "record");
+    def_copy.addRecordField("ipv4-address");
+    def_copy.addRecordField("uint32");
+    OptionCustomPtr option_copy(new OptionCustom(def_copy, Option::V6));
+    option_copy->writeAddress(IOAddress("192.0.0.2"));
+    option_copy->writeInteger<uint32_t>(12, 1);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the original option value.
+    option->writeInteger<uint16_t>(1000);
+
+    // The copied option should not be affected.
+    ASSERT_EQ(1, option_copy->getDataFieldsNum());
+    EXPECT_EQ(5555, option_copy->readInteger<uint16_t>());
+}
+
+TEST(OptionCopyTest, optionCustomConstructor) {
+    testOptionCustom(COPY);
+}
+
+TEST(OptionCopyTest, optionCustomClone) {
+    testOptionCustom(CLONE);
+}
+
+TEST(OptionCopyTest, optionCustomAssignment) {
+    testOptionCustom(ASSIGN);
+}
+
+// ************************ OptionOpaqueDataTuples ***********************
+
+/// @brief Test deep copy of option encapsulated by OptionOpaqueDataTuples type.
+///
+/// @param op_type Copy operation type.
+void testOptionOpaqueDataTuples(const OpType& op_type) {
+    OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1));
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "a string";
+    option->addTuple(tuple);
+    tuple = "another string";
+    option->addTuple(tuple);
+    OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10));
+    OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple_copy = "copy string";
+    option_copy->addTuple(tuple_copy);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    // Modify the value in the first tuple and add one more tuple.
+    tuple = "modified-first-tuple";
+    option->setTuple(0, tuple);
+    tuple = "modified-second-tuple";
+    option->setTuple(1, tuple);
+
+    // This should not affect the values in the original option.
+    ASSERT_EQ(2, option_copy->getTuplesNum());
+    EXPECT_TRUE(option_copy->getTuple(0).equals("a string"));
+    EXPECT_TRUE(option_copy->getTuple(1).equals("another string"));
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) {
+    testOptionOpaqueDataTuples(COPY);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesClone) {
+    testOptionOpaqueDataTuples(CLONE);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) {
+    testOptionOpaqueDataTuples(ASSIGN);
+}
+
+}

+ 7 - 1
src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -58,6 +58,12 @@ TEST(OptionOpaqueDataTuples, addTuple) {
     // for DHCPv6 option.
     OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
     EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue);
+
+    // Similarly, adding a tuple with 2 bytes long length field should
+    // fail for DHCPv4 option.
+    OptionOpaqueDataTuples data_tuple2(Option::V4, 65);
+    OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_2_BYTES);
+    EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue);
 }
 
 // This test checks that it is possible to replace existing tuple.

+ 55 - 0
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -593,6 +593,61 @@ TEST_F(Pkt4Test, options) {
     EXPECT_NO_THROW(pkt.reset());
 }
 
+// This test verifies that it is possible to control whether a pointer
+// to an option or a pointer to a copy of an option is returned by the
+// packet object.
+TEST_F(Pkt4Test, setCopyRetrievedOptions) {
+    // Create option 1 with two sub options.
+    OptionPtr option1(new Option(Option::V4, 1));
+    OptionPtr sub1(new Option(Option::V4, 1));
+    OptionPtr sub2(new Option(Option::V4, 2));
+
+    option1->addOption(sub1);
+    option1->addOption(sub2);
+
+    // Create option 2 with two sub options.
+    OptionPtr option2(new Option(Option::V4, 2));
+    OptionPtr sub3(new Option(Option::V4, 1));
+    OptionPtr sub4(new Option(Option::V4, 2));
+
+    option2->addOption(sub3);
+    option2->addOption(sub4);
+
+    // Add both options to a packet.
+    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+    pkt->addOption(option1);
+    pkt->addOption(option2);
+
+    // Retrieve options and make sure that the pointers to the original
+    // option instances are returned.
+    ASSERT_TRUE(option1 == pkt->getOption(1));
+    ASSERT_TRUE(option2 == pkt->getOption(2));
+
+    // Now force copying the options when they are retrieved.
+    pkt->setCopyRetrievedOptions(true);
+    EXPECT_TRUE(pkt->isCopyRetrievedOptions());
+
+    // Option pointer returned must point to a new instance of option 2.
+    OptionPtr option2_copy = pkt->getOption(2);
+    EXPECT_FALSE(option2 == option2_copy);
+
+    // Disable copying.
+    pkt->setCopyRetrievedOptions(false);
+    EXPECT_FALSE(pkt->isCopyRetrievedOptions());
+
+    // Expect that the original pointer is returned. This guarantees that
+    // option1 wasn't affected by copying option 2.
+    OptionPtr option1_copy = pkt->getOption(1);
+    EXPECT_TRUE(option1 == option1_copy);
+
+    // Again, enable copying options.
+    pkt->setCopyRetrievedOptions(true);
+
+    // This time a pointer to new option instance should be returned.
+    option1_copy = pkt->getOption(1);
+    EXPECT_FALSE(option1 == option1_copy);
+}
+
 // This test verifies that the options are unpacked from the packet correctly.
 TEST_F(Pkt4Test, unpackOptions) {
 

+ 25 - 0
src/lib/dhcp/tests/pkt4o6_unittest.cc

@@ -93,6 +93,31 @@ TEST_F(Pkt4o6Test, pack) {
     EXPECT_EQ(0, memcmp(&cp[8], &buffer4_[0], buffer4_.size()));
 }
 
+// This test verifies that the flag indicating that the retrieved options
+// should be copied is transferred between the DHCPv4 packet and the
+// DHCPv6 packet being a member of Pkt4o6 class.
+TEST_F(Pkt4o6Test, setCopyRetrievedOptions) {
+    // Create Pkt4o6 and initially expect that the flag is set to false.
+    Pkt4o6 pkt4o6(pkt4_, pkt6_);
+    ASSERT_FALSE(pkt4o6.isCopyRetrievedOptions());
+    Pkt6Ptr pkt6 = pkt4o6.getPkt6();
+    ASSERT_TRUE(pkt6);
+    ASSERT_FALSE(pkt6->isCopyRetrievedOptions());
+
+    // Set the flag to true for Pkt4o6.
+    pkt4o6.setCopyRetrievedOptions(true);
+    pkt6 = pkt4o6.getPkt6();
+    ASSERT_TRUE(pkt6);
+    EXPECT_TRUE(pkt6->isCopyRetrievedOptions());
+
+    // Repeat the same test but set the flag to false.
+    pkt4o6.setCopyRetrievedOptions(false);
+    EXPECT_FALSE(pkt4o6.isCopyRetrievedOptions());
+    pkt6 = pkt4o6.getPkt6();
+    ASSERT_TRUE(pkt6);
+    EXPECT_FALSE(pkt6->isCopyRetrievedOptions());
+}
+
 /// @todo: Add a test that handles actual DHCP4o6 traffic capture
 ///        once we get it. We should add the capture to pkt_captures{4,6}.cc
 }

+ 194 - 5
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -20,15 +20,16 @@
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <util/range_utilities.h>
-
 #include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <util/encode/hex.h>
 #include <gtest/gtest.h>
 
+#include <algorithm>
 #include <iostream>
 #include <sstream>
+#include <utility>
 
 #include <arpa/inet.h>
 
@@ -41,6 +42,38 @@ using boost::scoped_ptr;
 
 namespace {
 
+class NakedPkt6 : public Pkt6 {
+public:
+
+    /// @brief Constructor, used in replying to a message
+    ///
+    /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
+    /// @param transid transaction-id
+    /// @param proto protocol (TCP or UDP)
+    NakedPkt6(const uint8_t msg_type, const uint32_t transid,
+              const DHCPv6Proto& proto = UDP)
+        : Pkt6(msg_type, transid, proto) {
+    }
+
+    /// @brief Constructor, used in message transmission
+    ///
+    /// Creates new message. Transaction-id will randomized.
+    ///
+    /// @param buf pointer to a buffer of received packet content
+    /// @param len size of buffer of received packet content
+    /// @param proto protocol (usually UDP, but TCP will be supported eventually)
+    NakedPkt6(const uint8_t* buf, const uint32_t len,
+              const DHCPv6Proto& proto = UDP)
+        : Pkt6(buf, len, proto) {
+    }
+
+    using Pkt6::getNonCopiedOptions;
+    using Pkt6::getNonCopiedRelayOption;
+    using Pkt6::getNonCopiedAnyRelayOption;
+};
+
+typedef boost::shared_ptr<NakedPkt6> NakedPkt6Ptr;
+
 class Pkt6Test : public ::testing::Test {
 public:
     Pkt6Test() {
@@ -205,14 +238,14 @@ Pkt6* capture2() {
     // to be OptionBuffer format)
     isc::util::encode::decodeHex(hex_string, bin);
 
-    Pkt6* pkt = new Pkt6(&bin[0], bin.size());
+    NakedPkt6* pkt = new NakedPkt6(&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);
+    return (dynamic_cast<Pkt6*>(pkt));
 }
 
 TEST_F(Pkt6Test, unpack_solicit1) {
@@ -448,6 +481,92 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     EXPECT_EQ(0, options.size());
 }
 
+// Check that multiple options of the same type may be retrieved by using
+// Pkt6::getOptions or Pkt6::getNonCopiedOptions. In the former case, also
+// check that retrieved options are copied when Pkt6::setCopyRetrievedOptions
+// is enabled.
+TEST_F(Pkt6Test, getOptions) {
+    NakedPkt6 pkt(DHCPV6_SOLICIT, 1234);
+    OptionPtr opt1(new Option(Option::V6, 1));
+    OptionPtr opt2(new Option(Option::V6, 1));
+    OptionPtr opt3(new Option(Option::V6, 2));
+    OptionPtr opt4(new Option(Option::V6, 2));
+
+    pkt.addOption(opt1);
+    pkt.addOption(opt2);
+    pkt.addOption(opt3);
+    pkt.addOption(opt4);
+
+    // Retrieve options with option code 1.
+    OptionCollection options = pkt.getOptions(1);
+    ASSERT_EQ(2, options.size());
+
+    OptionCollection::const_iterator opt_it;
+
+    // Make sure that the first option is returned. We're using the pointer
+    // to opt1 to find the option.
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(1, opt1));
+    EXPECT_TRUE(opt_it != options.end());
+
+    // Make sure that the second option is returned.
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(1, opt2));
+    EXPECT_TRUE(opt_it != options.end());
+
+    // Retrieve options with option code 2.
+    options = pkt.getOptions(2);
+
+    // opt3 and opt4 should exist.
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(2, opt3));
+    EXPECT_TRUE(opt_it != options.end());
+
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(2, opt4));
+    EXPECT_TRUE(opt_it != options.end());
+
+    // Enable copying options when they are retrieved.
+    pkt.setCopyRetrievedOptions(true);
+
+    options = pkt.getOptions(1);
+    ASSERT_EQ(2, options.size());
+
+    // Both retrieved options should be copied so an attempt to find them
+    // using option pointer should fail. Original pointers should have
+    // been replaced with new instances.
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(1, opt1));
+    EXPECT_TRUE(opt_it == options.end());
+
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(1, opt2));
+    EXPECT_TRUE(opt_it == options.end());
+
+    // Return instances of options with the option code 1 and make sure
+    // that copies of the options were used to replace original options
+    // in the packet.
+    OptionCollection options_modified = pkt.getNonCopiedOptions(1);
+    for (OptionCollection::const_iterator opt_it_modified = options_modified.begin();
+         opt_it_modified != options_modified.end(); ++opt_it_modified) {
+        opt_it = std::find(options.begin(), options.end(), *opt_it_modified);
+        ASSERT_TRUE(opt_it != options.end());
+    }
+
+    // Let's check that remaining two options haven't been affected by
+    // retrieving the options with option code 1.
+    options = pkt.getNonCopiedOptions(2);
+    ASSERT_EQ(2, options.size());
+
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(2, opt3));
+    EXPECT_TRUE(opt_it != options.end());
+
+    opt_it = std::find(options.begin(), options.end(),
+                       std::pair<const unsigned int, OptionPtr>(2, opt4));
+    EXPECT_TRUE(opt_it != options.end());
+}
+
 TEST_F(Pkt6Test, Timestamp) {
     boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
 
@@ -750,12 +869,35 @@ TEST_F(Pkt6Test, relayPack) {
     EXPECT_EQ(0, memcmp(expected0, &binary[0], 16));
 }
 
+TEST_F(Pkt6Test, getRelayOption) {
+    NakedPkt6Ptr msg(dynamic_cast<NakedPkt6*>(capture2()));
+    ASSERT_TRUE(msg);
+
+    ASSERT_NO_THROW(msg->unpack());
+    ASSERT_EQ(2, msg->relay_info_.size());
+
+    OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+    ASSERT_TRUE(opt_iface_id);
 
-// This test verified that options added by relays to the message can be
+    OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+    ASSERT_TRUE(opt_iface_id_returned);
+
+    EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+
+    msg->setCopyRetrievedOptions(true);
+
+    opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+    EXPECT_FALSE(opt_iface_id == opt_iface_id_returned);
+
+    opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+    EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+}
+
+// This test verifies that options added by relays to the message can be
 // accessed and retrieved properly
 TEST_F(Pkt6Test, getAnyRelayOption) {
 
-    boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+    boost::scoped_ptr<NakedPkt6> msg(new NakedPkt6(DHCPV6_ADVERTISE, 0x020304));
     msg->addOption(generateRandomOption(300));
 
     // generate options for relay1
@@ -813,22 +955,69 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
     ASSERT_TRUE(opt);
     EXPECT_TRUE(opt->equals(relay3_opt1));
+    EXPECT_TRUE(opt == relay3_opt1);
 
     // We want to ge that one inserted by relay1 (first match, starting from
     // closest to the server.
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
     ASSERT_TRUE(opt);
     EXPECT_TRUE(opt->equals(relay1_opt1));
+    EXPECT_TRUE(opt == relay1_opt1);
 
     // We just want option from the first relay (closest to the client)
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
     ASSERT_TRUE(opt);
     EXPECT_TRUE(opt->equals(relay3_opt1));
+    EXPECT_TRUE(opt == relay3_opt1);
 
     // We just want option from the last relay (closest to the server)
     opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
     ASSERT_TRUE(opt);
     EXPECT_TRUE(opt->equals(relay1_opt1));
+    EXPECT_TRUE(opt == relay1_opt1);
+
+    // Enable copying options when they are retrieved and redo the tests
+    // but expect that options are still equal but different pointers
+    // are returned.
+    msg->setCopyRetrievedOptions(true);
+
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equals(relay3_opt1));
+    EXPECT_FALSE(opt == relay3_opt1);
+    // Test that option copy has replaced the original option within the
+    // packet. We achive that by calling a variant of the method which
+    // retrieved non-copied option.
+    relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(relay3_opt1);
+    EXPECT_TRUE(opt == relay3_opt1);
+
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equals(relay1_opt1));
+    EXPECT_FALSE(opt == relay1_opt1);
+    relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(relay1_opt1);
+    EXPECT_TRUE(opt == relay1_opt1);
+
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equals(relay3_opt1));
+    EXPECT_FALSE(opt == relay3_opt1);
+    relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+    ASSERT_TRUE(relay3_opt1);
+    EXPECT_TRUE(opt == relay3_opt1);
+
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equals(relay1_opt1));
+    EXPECT_FALSE(opt == relay1_opt1);
+    relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+    ASSERT_TRUE(relay1_opt1);
+    EXPECT_TRUE(opt == relay1_opt1);
+
+    // Disable copying options and continue with other tests.
+    msg->setCopyRetrievedOptions(false);
 
     // Let's try to ask for something that is inserted by the middle relay
     // only.

+ 18 - 0
src/lib/dhcpsrv/alloc_engine.cc

@@ -1050,6 +1050,9 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
         // Pass necessary arguments
 
         // Pass the original packet
@@ -1120,6 +1123,9 @@ Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
         // Pass necessary arguments
 
         // Pass the original packet
@@ -1338,6 +1344,9 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
         // Delete all previous arguments
         callout_handle->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
         // Pass the original packet
         callout_handle->setArgument("query6", ctx.query_);
 
@@ -2524,6 +2533,9 @@ AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr) {
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
         // Pass necessary arguments
         // Pass the original client query
         ctx.callout_handle_->setArgument("query4", ctx.query_);
@@ -2629,6 +2641,9 @@ AllocEngine::renewLease4(const Lease4Ptr& lease,
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
         // Subnet from which we do the allocation. Convert the general subnet
         // pointer to a pointer to a Subnet4.  Note that because we are using
         // boost smart pointers here, we need to do the cast using the boost
@@ -2703,6 +2718,9 @@ AllocEngine::reuseExpiredLease4(Lease4Ptr& expired,
     if (ctx.callout_handle_ &&  HooksManager::getHooksManager()
         .calloutsPresent(hook_index_lease4_select_)) {
 
+        // Enable copying options from the packet within hook library.
+        ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
         // Delete all previous arguments
         ctx.callout_handle_->deleteAllArguments();
 

+ 1 - 0
src/lib/dhcpsrv/parsers/.gitignore

@@ -0,0 +1 @@
+/.dirstamp

+ 22 - 0
src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc

@@ -45,6 +45,7 @@ public:
         callback_addr_original_ = IOAddress("::");
         callback_addr_updated_ = IOAddress("::");
         callback_qry_pkt6_.reset();
+        callback_qry_options_copy_ = false;
     }
 
     /// callback that stores received callout name and received values
@@ -61,6 +62,12 @@ public:
         callback_addr_original_ = callback_lease6_->addr_;
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt6_) {
+            callback_qry_options_copy_ =
+                callback_qry_pkt6_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -102,6 +109,7 @@ public:
     static bool callback_fake_allocation_;
     static vector<string> callback_argument_names_;
     static Pkt6Ptr callback_qry_pkt6_;
+    static bool callback_qry_options_copy_;
 };
 
 // For some reason intialization within a class makes the linker confused.
@@ -122,6 +130,7 @@ Lease6Ptr HookAllocEngine6Test::callback_lease6_;
 bool HookAllocEngine6Test::callback_fake_allocation_;
 vector<string> HookAllocEngine6Test::callback_argument_names_;
 Pkt6Ptr HookAllocEngine6Test::callback_qry_pkt6_;
+bool HookAllocEngine6Test::callback_qry_options_copy_;
 
 // This test checks if the lease6_select callout is executed and expected
 // parameters as passed.
@@ -195,6 +204,8 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
     sort(expected_argument_names.begin(), expected_argument_names.end());
 
     EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test checks if lease6_select callout is able to override the values
@@ -282,6 +293,7 @@ public:
         callback_addr_original_ = IOAddress("::");
         callback_addr_updated_ = IOAddress("::");
         callback_qry_pkt4_.reset();
+        callback_qry_options_copy_ = false;
     }
 
     /// callback that stores received callout name and received values
@@ -298,6 +310,12 @@ public:
         callback_addr_original_ = callback_lease4_->addr_;
 
         callback_argument_names_ = callout_handle.getArgumentNames();
+
+        if (callback_qry_pkt4_) {
+            callback_qry_options_copy_ =
+                callback_qry_pkt4_->isCopyRetrievedOptions();
+        }
+
         return (0);
     }
 
@@ -337,6 +355,7 @@ public:
     static bool callback_fake_allocation_;
     static vector<string> callback_argument_names_;
     static Pkt4Ptr callback_qry_pkt4_;
+    static bool callback_qry_options_copy_;
 };
 
 // For some reason intialization within a class makes the linker confused.
@@ -356,6 +375,7 @@ Lease4Ptr HookAllocEngine4Test::callback_lease4_;
 bool HookAllocEngine4Test::callback_fake_allocation_;
 vector<string> HookAllocEngine4Test::callback_argument_names_;
 Pkt4Ptr HookAllocEngine4Test::callback_qry_pkt4_;
+bool HookAllocEngine4Test::callback_qry_options_copy_;
 
 // This test checks if the lease4_select callout is executed and expected
 // parameters as passed.
@@ -428,6 +448,8 @@ TEST_F(HookAllocEngine4Test, lease4_select) {
     expected_argument_names.push_back("query4");
     expected_argument_names.push_back("subnet4");
     EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    EXPECT_TRUE(callback_qry_options_copy_);
 }
 
 // This test checks if lease4_select callout is able to override the values

+ 1 - 0
src/lib/dns/rdata/generic/detail/.gitignore

@@ -0,0 +1 @@
+/.dirstamp

+ 1 - 1
src/lib/eval/.gitignore

@@ -1,3 +1,3 @@
 /eval_messages.cc
 /eval_messages.h
-/s-messages
+/s-messages

+ 1 - 1
src/lib/eval/evaluate.cc

@@ -9,7 +9,7 @@
 namespace isc {
 namespace dhcp {
 
-bool evaluate(const Expression& expr, const Pkt& pkt) {
+bool evaluate(const Expression& expr, Pkt& pkt) {
     ValueStack values;
     for (Expression::const_iterator it = expr.begin();
          it != expr.end(); ++it) {

+ 2 - 2
src/lib/eval/evaluate.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,7 +22,7 @@ namespace dhcp {
 ///        stack at the end of the evaluation
 /// @throw EvalTypeError if the value at the top of the stack at the
 ///        end of the evaluation is not "false" or "true"
-bool evaluate(const Expression& expr, const Pkt& pkt);
+bool evaluate(const Expression& expr, Pkt& pkt);
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 1 - 0
src/lib/eval/tests/.gitignore

@@ -0,0 +1 @@
+/libeval_unittests

+ 17 - 17
src/lib/eval/token.cc

@@ -18,7 +18,7 @@ using namespace isc::dhcp;
 using namespace std;
 
 void
-TokenString::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
     // Literals only push, nothing to pop
     values.push(value_);
 
@@ -56,7 +56,7 @@ TokenHexString::TokenHexString(const string& str) : value_("") {
 }
 
 void
-TokenHexString::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
     // Literals only push, nothing to pop
     values.push(value_);
 
@@ -82,7 +82,7 @@ TokenIpAddress::TokenIpAddress(const string& addr) : value_("") {
 }
 
 void
-TokenIpAddress::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenIpAddress::evaluate(Pkt& /*pkt*/, ValueStack& values) {
     // Literals only push, nothing to pop
     values.push(value_);
 
@@ -93,12 +93,12 @@ TokenIpAddress::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 OptionPtr
-TokenOption::getOption(const Pkt& pkt) {
+TokenOption::getOption(Pkt& pkt) {
     return (pkt.getOption(option_code_));
 }
 
 void
-TokenOption::evaluate(const Pkt& pkt, ValueStack& values) {
+TokenOption::evaluate(Pkt& pkt, ValueStack& values) {
     OptionPtr opt = getOption(pkt);
     std::string opt_str;
     if (opt) {
@@ -141,7 +141,7 @@ TokenRelay4Option::TokenRelay4Option(const uint16_t option_code,
     :TokenOption(option_code, rep_type) {
 }
 
-OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) {
+OptionPtr TokenRelay4Option::getOption(Pkt& pkt) {
 
     // Check if there is Relay Agent Option.
     OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS);
@@ -154,7 +154,7 @@ OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) {
 }
 
 void
-TokenPkt4::evaluate(const Pkt& pkt, ValueStack& values) {
+TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) {
 
     vector<uint8_t> binary;
     string type_str;
@@ -239,7 +239,7 @@ TokenPkt4::evaluate(const Pkt& pkt, ValueStack& values) {
 }
 
 void
-TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenEqual::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() < 2) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
@@ -266,7 +266,7 @@ TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 void
-TokenSubstring::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenSubstring::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() < 3) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
@@ -365,7 +365,7 @@ TokenSubstring::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 void
-TokenConcat::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() < 2) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
@@ -391,7 +391,7 @@ TokenConcat::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 void
-TokenNot::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() == 0) {
         isc_throw(EvalBadStack, "Incorrect empty stack.");
@@ -414,7 +414,7 @@ TokenNot::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 void
-TokenAnd::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenAnd::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() < 2) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
@@ -442,7 +442,7 @@ TokenAnd::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 }
 
 void
-TokenOr::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) {
 
     if (values.size() < 2) {
         isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
@@ -469,12 +469,12 @@ TokenOr::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
         .arg('\'' + values.top() + '\'');
 }
 
-OptionPtr TokenRelay6Option::getOption(const Pkt& pkt) {
+OptionPtr TokenRelay6Option::getOption(Pkt& pkt) {
 
     try {
         // Check if it's a Pkt6.  If it's not the dynamic_cast will
         // throw std::bad_cast.
-        const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt);
+        Pkt6& pkt6 = dynamic_cast<Pkt6&>(pkt);
 
         try {
             // Now that we have the right type of packet we can
@@ -496,7 +496,7 @@ OptionPtr TokenRelay6Option::getOption(const Pkt& pkt) {
 }
 
 void
-TokenRelay6Field::evaluate(const Pkt& pkt, ValueStack& values) {
+TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) {
 
     vector<uint8_t> binary;
     string type_str;
@@ -549,7 +549,7 @@ TokenRelay6Field::evaluate(const Pkt& pkt, ValueStack& values) {
 }
 
 void
-TokenPkt6::evaluate(const Pkt& pkt, ValueStack& values) {
+TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) {
 
     vector<uint8_t> binary;
     string type_str;

+ 17 - 17
src/lib/eval/token.h

@@ -75,7 +75,7 @@ public:
     ///
     /// @param pkt - packet being classified
     /// @param values - stack of values with previously evaluated tokens
-    virtual void evaluate(const Pkt& pkt, ValueStack& values) = 0;
+    virtual void evaluate(Pkt& pkt, ValueStack& values) = 0;
 
     /// @brief Virtual destructor
     virtual ~Token() {}
@@ -124,7 +124,7 @@ public:
     ///
     /// @param pkt (ignored)
     /// @param values (represented string will be pushed here)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
 protected:
     std::string value_; ///< Constant value
@@ -149,7 +149,7 @@ public:
     ///
     /// @param pkt (ignored)
     /// @param values (represented string will be pushed here)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
 protected:
     std::string value_; ///< Constant value
@@ -171,7 +171,7 @@ public:
     ///
     /// @param pkt (ignored)
     /// @param values (represented IP address will be pushed here)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
 protected:
     ///< Constant value (empty string if the IP address cannot be converted)
@@ -222,7 +222,7 @@ public:
     ///
     /// @param pkt specified option will be extracted from this packet (if present)
     /// @param values value of the option will be pushed here (or "")
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
     /// @brief Returns option-code
     ///
@@ -254,7 +254,7 @@ protected:
     ///
     /// @param pkt the option will be retrieved from here
     /// @return option instance (or NULL if not found)
-    virtual OptionPtr getOption(const Pkt& pkt);
+    virtual OptionPtr getOption(Pkt& pkt);
 
     uint16_t option_code_; ///< Code of the option to be extracted
     RepresentationType representation_type_; ///< Representation type.
@@ -285,7 +285,7 @@ protected:
     /// @brief Attempts to obtain specified sub-option of option 82 from the packet
     /// @param pkt DHCPv4 packet (that hopefully contains option 82)
     /// @return found sub-option from option 82
-    virtual OptionPtr getOption(const Pkt& pkt);
+    virtual OptionPtr getOption(Pkt& pkt);
 };
 
 /// @brief Token that represents fields of a DHCPv4 packet.
@@ -328,7 +328,7 @@ public:
     ///
     /// @param pkt - fields will be extracted from here
     /// @param values - stack of values (1 result will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
     /// @brief Returns field type
     ///
@@ -364,7 +364,7 @@ public:
     /// @param pkt (unused)
     /// @param values - stack of values (2 arguments will be popped, 1 result
     ///        will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents the substring operator (returns a portion
@@ -421,7 +421,7 @@ public:
     /// @param pkt (unused)
     /// @param values - stack of values (3 arguments will be popped, 1 result
     ///        will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents concat operator (concatenates two other tokens)
@@ -444,7 +444,7 @@ public:
     /// @param pkt (unused)
     /// @param values - stack of values (2 arguments will be popped, 1 result
     ///        will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents logical negation operator
@@ -469,7 +469,7 @@ public:
     ///
     /// @param pkt (unused)
     /// @param values - stack of values (logical top value negated)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents logical and operator
@@ -494,7 +494,7 @@ public:
     /// @param pkt (unused)
     /// @param values - stack of values (2 arguments will be popped, 1 result
     ///        will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents logical or operator
@@ -519,7 +519,7 @@ public:
     /// @param pkt (unused)
     /// @param values - stack of values (2 arguments will be popped, 1 result
     ///        will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 };
 
 /// @brief Token that represents a value of an option within a DHCPv6 relay
@@ -563,7 +563,7 @@ protected:
     /// @brief Attempts to obtain specified option from the specified relay block
     /// @param pkt DHCPv6 packet that hopefully contains the proper relay block
     /// @return option instance if available
-    virtual OptionPtr getOption(const Pkt& pkt);
+    virtual OptionPtr getOption(Pkt& pkt);
 
     uint8_t nest_level_; ///< nesting level of the relay block to use
 };
@@ -606,7 +606,7 @@ public:
     ///
     /// @param pkt fields will be extracted from here
     /// @param values - stack of values (1 result will be pushed)
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
     /// @brief Returns nest-level
     ///
@@ -666,7 +666,7 @@ public:
     ///
     /// @param pkt - packet from which to extract the fields
     /// @param values - stack of values, 1 result will be pushed
-    void evaluate(const Pkt& pkt, ValueStack& values);
+    void evaluate(Pkt& pkt, ValueStack& values);
 
     /// @brief Returns field type
     ///

+ 1 - 1
src/lib/testutils/.gitignore

@@ -1 +1 @@
-/dhcp_test_lib.sh
+/dhcp_test_lib.sh

+ 1 - 0
src/lib/util/encode/.gitignore

@@ -0,0 +1 @@
+/.dirstamp

+ 1 - 0
src/lib/util/random/.gitignore

@@ -0,0 +1 @@
+/.dirstamp

+ 1 - 1
src/lib/util/tests/.gitignore

@@ -1,2 +1,2 @@
 /run_unittests
-/process_spawn_app.sh
+/process_spawn_app.sh