Commit fbd175e0 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[4497] Implemented copying options on retrieval from packet.

parent 21053d3a
......@@ -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
}
......
......@@ -245,18 +245,67 @@ public:
/// 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);
OptionPtr getOption(const uint16_t type) const {
return (getNonCopiedOption(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).
void setCopyRetrievedOptions(const bool copy) {
copy_retrieved_options_ = copy;
}
/// @brief Update packet timestamp.
///
......@@ -615,6 +664,12 @@ protected:
/// 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_;
......
......@@ -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.");
}
......
......@@ -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) {
......
......@@ -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 Pk6::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_option_ 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 serch 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_option_ 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_option_ flag is set to true.
///
/// @param option_code 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
///
......
......@@ -593,6 +593,59 @@ 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);
// 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 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) {
......
......@@ -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);