Commit 01c6801b authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

Merge branch 'trac1228'

Conflicts:
	ChangeLog
parents 0a3592ef 31d5a4f6
315. [func] tomek
libdhcp: Support for DHCPv4 packet manipulation is now implemented.
All fixed fields are now supported. Generic support for DHCPv4
options is available (both parsing and assembly). There is no code
that uses this new functionality yet, so it is not usable directly
at this time. This code will be used by upcoming b10-dhcp4 daemon.
(Trac #1228, git 31d5a4f66b18cca838ca1182b9f13034066427a7)
314. [bug] jelte
b10-xfrin would previously initiate incoming transfers upon
receiving NOTIFY messages from any address (if the zone was
......
......@@ -130,13 +130,15 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
ASSERT_TRUE( tmp );
EXPECT_EQ(clientid->getType(), tmp->getType() );
ASSERT_EQ(clientid->len(), tmp->len() );
EXPECT_FALSE(memcmp(clientid->getData(), tmp->getData(), tmp->len() ) );
EXPECT_TRUE( clientid->getData() == tmp->getData() );
// check that server included its server-id
tmp = reply->getOption(D6O_SERVERID);
EXPECT_EQ(tmp->getType(), srv->getServerID()->getType() );
ASSERT_EQ(tmp->len(), srv->getServerID()->len() );
EXPECT_FALSE( memcmp(tmp->getData(), srv->getServerID()->getData(),
tmp->len()) );
EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
// more checks to be implemented
delete srv;
......
......@@ -14,16 +14,17 @@
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include "dhcp/libdhcp.h"
#include <util/buffer.h>
#include <dhcp/libdhcp.h>
#include "config.h"
#include "dhcp6.h"
#include "option.h"
#include "option6_ia.h"
#include "option6_iaaddr.h"
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
......@@ -32,7 +33,7 @@ unsigned int
LibDHCP::unpackOptions6(const boost::shared_array<uint8_t> buf,
unsigned int buf_len,
unsigned int offset, unsigned int parse_len,
isc::dhcp::Option::Option6Collection& options) {
isc::dhcp::Option::OptionCollection& options) {
if (offset + parse_len > buf_len) {
isc_throw(OutOfRange, "Option parse failed. Tried to parse "
<< parse_len << " bytes at offset " << offset
......@@ -83,13 +84,41 @@ LibDHCP::unpackOptions6(const boost::shared_array<uint8_t> buf,
return (offset);
}
void
LibDHCP::unpackOptions4(const std::vector<uint8_t>& buf,
isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
// 2 - header of DHCPv4 option
while (offset + 2 <= buf.size()) {
uint8_t opt_type = buf[offset++];
uint8_t opt_len = buf[offset++];
if (offset + opt_len > buf.size() ) {
isc_throw(OutOfRange, "Option parse failed. Tried to parse "
<< offset + opt_len << " bytes from " << buf.size()
<< "-byte long buffer.");
}
boost::shared_ptr<Option> opt;
switch(opt_type) {
default:
opt = boost::shared_ptr<Option>(new Option(Option::V4, opt_type,
buf.begin()+offset,
buf.begin()+offset+opt_len));
}
options.insert(pair<int, boost::shared_ptr<Option> >(opt_type, opt));
offset += opt_len;
}
}
unsigned int
LibDHCP::packOptions6(boost::shared_array<uint8_t> data,
unsigned int data_len,
unsigned int offset,
const isc::dhcp::Option::Option6Collection& options) {
const isc::dhcp::Option::OptionCollection& options) {
try {
for (isc::dhcp::Option::Option6Collection::const_iterator it = options.begin();
for (Option::OptionCollection::const_iterator it = options.begin();
it != options.end();
++it) {
unsigned short opt_len = (*it).second->len();
......@@ -97,7 +126,7 @@ LibDHCP::packOptions6(boost::shared_array<uint8_t> data,
isc_throw(OutOfRange, "Failed to build option " <<
(*it).first << ": out of buffer");
}
offset = (*it).second->pack(data, data_len, offset);
offset = it->second->pack(data, data_len, offset);
}
}
catch (const Exception& e) {
......@@ -107,6 +136,17 @@ LibDHCP::packOptions6(boost::shared_array<uint8_t> data,
return (offset);
}
void
LibDHCP::packOptions(isc::util::OutputBuffer& buf,
const Option::OptionCollection& options) {
for (Option::OptionCollection::const_iterator it = options.begin();
it != options.end();
++it) {
it->second->pack4(buf);
}
}
bool
LibDHCP::OptionFactoryRegister(Option::Universe u,
unsigned short opt_type,
......
......@@ -16,7 +16,8 @@
#define LIBDHCP_H_
#include <iostream>
#include "dhcp/pkt6.h"
#include <util/buffer.h>
#include <dhcp/pkt6.h>
namespace isc {
namespace dhcp {
......@@ -39,8 +40,27 @@ public:
static unsigned int
packOptions6(boost::shared_array<uint8_t> buf, unsigned int buf_len,
unsigned int offset,
const isc::dhcp::Option::Option6Collection& options);
const isc::dhcp::Option::OptionCollection& options);
/// @brief Stores options in a buffer.
///
/// Stores all options defined in options containers in a on-wire
/// format in output buffer specified by buf.
///
/// May throw different exceptions if option assembly fails. There
/// may be different reasons (option too large, option malformed,
/// too many options etc.)
///
/// @param buf
/// @param options
static void
packOptions(isc::util::OutputBuffer& buf,
const isc::dhcp::Option::OptionCollection& options);
static void
unpackOptions4(const std::vector<uint8_t>& buf,
isc::dhcp::Option::OptionCollection& options);
///
/// Parses provided buffer and creates Option objects.
///
......@@ -57,7 +77,7 @@ public:
static unsigned int
unpackOptions6(const boost::shared_array<uint8_t> buf, unsigned int buf_len,
unsigned int offset, unsigned int parse_len,
isc::dhcp::Option::Option6Collection& options_);
isc::dhcp::Option::OptionCollection& options_);
///
/// Registers factory method that produces options of specific option types.
......
......@@ -29,50 +29,117 @@ using namespace isc::dhcp;
using namespace isc::util;
Option::Option(Universe u, unsigned short type)
:universe_(u), type_(type), data_len_(0) {
:universe_(u), type_(type) {
if ((u == V4) && (type > 255)) {
isc_throw(BadValue, "Can't create V4 option of type "
<< type << ", V4 options are in range 0..255");
}
}
Option::Option(Universe u, unsigned short type,
const boost::shared_array<uint8_t>& buf,
unsigned int offset, unsigned int len)
:universe_(u), type_(type), data_(buf),
data_len_(len), offset_(offset)
{
:universe_(u), type_(type),
offset_(offset)
{
uint8_t* ptr = &buf[offset];
data_ = std::vector<uint8_t>(ptr, ptr + len);
check();
}
Option::Option(Universe u, unsigned short type, std::vector<uint8_t>& data)
:universe_(u), type_(type), data_(data) {
check();
}
// sanity checks
// TODO: universe must be in V4 and V6
Option::Option(Universe u, uint16_t type, vector<uint8_t>::const_iterator first,
vector<uint8_t>::const_iterator last)
:universe_(u), type_(type), data_(std::vector<uint8_t>(first,last)) {
check();
}
void
Option::check() {
if ( (universe_ != V4) && (universe_ != V6) ) {
isc_throw(BadValue, "Invalid universe type specified."
<< "Only V4 and V6 are allowed.");
}
if (universe_ == V4) {
if (type_ > 255) {
isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big."
<< "For DHCPv4 allowed type range is 0..255");
} else if (data_.size() > 255) {
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
/// TODO Larger options can be stored as separate instances
/// of DHCPv4 options. Clients MUST concatenate them.
/// Fortunately, there are no such large options used today.
}
}
// no need to check anything for DHCPv6. It allows full range (0-64k) of
// both types and data size.
}
unsigned int
Option::pack(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset) {
if (universe_ != V6) {
isc_throw(BadValue, "Failed to pack " << type_ << " option. Do not "
<< "use this method for options other than DHCPv6.");
}
return pack6(buf, buf_len, offset);
}
void
Option::pack4(isc::util::OutputBuffer& buf) {
switch (universe_) {
case V4:
return pack4(buf, buf_len, offset);
case V4: {
if (data_.size() > 255) {
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big."
<< "At most 255 bytes are supported.");
/// TODO Larger options can be stored as separate instances
/// of DHCPv4 options. Clients MUST concatenate them.
/// Fortunately, there are no such large options used today.
}
buf.writeUint8(type_);
buf.writeUint8(len() - getHeaderLen());
buf.writeData(&data_[0], data_.size());
LibDHCP::packOptions(buf, options_);
return;
}
case V6:
return pack6(buf, buf_len, offset);
/// TODO: Do we need a sanity check for option size here?
buf.writeUint16(type_);
buf.writeUint16(len() - getHeaderLen());
LibDHCP::packOptions(buf, options_);
return;
default:
isc_throw(BadValue, "Unknown universe defined for Option " << type_);
isc_throw(OutOfRange, "Invalid universe type" << universe_);
}
}
unsigned int
Option::pack4(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset) {
if ( offset+len() > buf_len ) {
if (offset + len() > buf_len) {
isc_throw(OutOfRange, "Failed to pack v4 option=" <<
type_ << ",len=" << data_len_ << ": too small buffer.");
type_ << ",len=" << len() << ": too small buffer.");
}
uint8_t *ptr = &buf[offset];
ptr[0] = type_;
ptr[1] = data_len_;
ptr[1] = len() - getHeaderLen();
ptr += 2;
memcpy(ptr, &data_[0], data_len_);
memcpy(ptr, &data_[0], data_.size());
return offset + len();
}
......@@ -81,22 +148,22 @@ unsigned int
Option::pack6(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset) {
if ( offset+len() > buf_len ) {
if (offset+len() > buf_len) {
isc_throw(OutOfRange, "Failed to pack v6 option=" <<
type_ << ",len=" << len() << ": too small buffer.");
}
uint8_t * ptr = &buf[offset];
uint8_t* ptr = &buf[offset];
ptr = writeUint16(type_, ptr);
ptr = writeUint16(len() - getHeaderLen(), ptr);
if (data_len_)
memcpy(ptr, &data_[offset_], data_len_);
if (! data_.empty())
memcpy(ptr, &data_[0], data_.size());
// end of fixed part of this option
offset += OPTION6_HDR_LEN + data_len_;
offset += OPTION6_HDR_LEN + data_.size();
return LibDHCP::packOptions6(buf, buf_len, offset, options_);
}
......@@ -140,22 +207,27 @@ Option::unpack6(const boost::shared_array<uint8_t>& buf,
<< "): too small buffer.");
}
data_ = buf;
uint8_t* ptr = &buf[offset];
data_ = std::vector<uint8_t>(ptr, ptr + parse_len);
offset_ = offset;
data_len_ = buf_len;
return LibDHCP::unpackOptions6(buf, buf_len, offset, parse_len,
options_);
return (offset+parse_len);
//return LibDHCP::unpackOptions6(buf, buf_len, offset, parse_len,
// options_);
}
/// Returns length of the complete option (data length + DHCPv4/DHCPv6
/// option header)
unsigned short
Option::len() {
// length of the whole option is header and data stored in this option...
int length = getHeaderLen() + data_len_;
int length = getHeaderLen() + data_.size();
// ... and sum of lengths of all suboptions
for (Option::Option6Collection::iterator it = options_.begin();
for (Option::OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
......@@ -177,16 +249,9 @@ Option::valid() {
return (true);
}
void
isc::dhcp::Option::addOption(boost::shared_ptr<isc::dhcp::Option> opt) {
options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(),
opt));
}
boost::shared_ptr<isc::dhcp::Option>
Option::getOption(unsigned short opt_type) {
isc::dhcp::Option::Option6Collection::const_iterator x =
isc::dhcp::Option::OptionCollection::const_iterator x =
options_.find(opt_type);
if ( x != options_.end() ) {
return (*x).second;
......@@ -196,7 +261,7 @@ Option::getOption(unsigned short opt_type) {
bool
Option::delOption(unsigned short opt_type) {
isc::dhcp::Option::Option6Collection::iterator x = options_.find(opt_type);
isc::dhcp::Option::OptionCollection::iterator x = options_.find(opt_type);
if ( x != options_.end() ) {
options_.erase(x);
return true; // delete successful
......@@ -208,22 +273,22 @@ Option::delOption(unsigned short opt_type) {
std::string Option::toText(int indent /* =0 */ ) {
std::stringstream tmp;
for (int i=0; i<indent; i++)
for (int i = 0; i < indent; i++)
tmp << " ";
tmp << "type=" << type_ << ", len=" << data_len_ << ": ";
tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ": ";
for (unsigned int i=0; i<data_len_; i++) {
for (unsigned int i = 0; i < data_.size(); i++) {
if (i) {
tmp << ":";
}
tmp << setfill('0') << setw(2) << hex
<< static_cast<unsigned short>(data_[offset_+i]);
<< static_cast<unsigned short>(data_[i]);
}
// print suboptions
for (Option6Collection::const_iterator opt=options_.begin();
opt!=options_.end();
for (OptionCollection::const_iterator opt = options_.begin();
opt != options_.end();
++opt) {
tmp << (*opt).second->toText(indent+2);
}
......@@ -235,13 +300,9 @@ Option::getType() {
return type_;
}
uint8_t*
const std::vector<uint8_t>&
Option::getData() {
if (data_len_) {
return (&data_[offset_]);
} else {
return (NULL);
}
return (data_);
}
unsigned short
......@@ -255,6 +316,18 @@ Option::getHeaderLen() {
return 0; // should not happen
}
void
Option::addOption(boost::shared_ptr<Option> opt) {
if (universe_ == V4) {
// check for uniqueness (DHCPv4 options must be unique)
if (getOption(opt->getType())) {
isc_throw(BadValue, "Option " << opt->getType()
<< " already present in this message.");
}
}
options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
}
Option::~Option() {
}
......@@ -17,8 +17,10 @@
#include <string>
#include <map>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>
#include <util/buffer.h>
namespace isc {
namespace dhcp {
......@@ -34,13 +36,9 @@ public:
/// defines option universe DHCPv4 or DHCPv6
enum Universe { V4, V6 };
/// a collection of DHCPv4 options
typedef std::map<unsigned int, boost::shared_ptr<Option> >
Option4Collection;
/// a collection of DHCPv6 options
typedef std::multimap<unsigned int, boost::shared_ptr<Option> >
Option6Collection;
OptionCollection;
/// @brief a factory function prototype
///
......@@ -80,11 +78,55 @@ public:
const boost::shared_array<uint8_t>& buf, unsigned int offset,
unsigned int len);
/// @brief writes option in wire-format to buf
/// @brief Constructor, used for received options.
///
/// This constructor takes vector<uint8_t>& which is used in cases
/// when content of the option will be copied and stored within
/// option object. V4 Options follow that approach already.
/// TODO Migrate V6 options to that approach.
///
/// @param u specifies universe (V4 or V6)
/// @param type option type (0-255 for V4 and 0-65535 for V6)
/// @param data content of the option
Option(Universe u, unsigned short type, std::vector<uint8_t>& data);
/// @brief Constructor, used for received options.
///
/// This contructor is similar to the previous one, but it does not take
/// the whole vector<uint8_t>, but rather subset of it.
///
/// TODO: This can be templated to use different containers, not just
/// vector. Prototype should look like this:
/// template<typename InputIterator> Option(Universe u, uint16_t type,
/// InputIterator first, InputIterator last);
///
/// vector<int8_t> myData;
/// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1)
/// This will create DHCPv4 option of type 123 that contains data from
/// trimmed (first and last byte removed) myData vector.
///
/// @param u specifies universe (V4 or V6)
/// @param type option type (0-255 for V4 and 0-65535 for V6)
/// @param first iterator to the first element that should be copied
/// @param last iterator to the next element after the last one
/// to be copied.
Option(Universe u, uint16_t type,
std::vector<uint8_t>::const_iterator first,
std::vector<uint8_t>::const_iterator last);
/// @brief returns option universe (V4 or V6)
///
/// @return universe type
Universe
getUniverse() { return universe_; };
/// @brief Writes option in wire-format to a buffer.
///
/// Writes option in wire-format to buffer, returns pointer to first unused
/// byte after stored option (that is useful for writing options one after
/// another)
/// another). Used in DHCPv6 options.
///
/// TODO: Migrate DHCPv6 code to pack(OutputBuffer& buf) version
///
/// @param buf pointer to a buffer
/// @param buf_len length of the buffer
......@@ -93,10 +135,21 @@ public:
/// @return offset to first unused byte after stored option
///
virtual unsigned int
pack(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
pack(boost::shared_array<uint8_t>& buf, unsigned int buf_len,
unsigned int offset);
/// @brief Writes option in a wire-format to a buffer.
///
/// Method will throw if option storing fails for some reason.
///
/// TODO Once old (DHCPv6) implementation is rewritten,
/// unify pack4() and pack6() and rename them to just pack().
///
/// @param buf output buffer (option will be stored there)
virtual void
pack4(isc::util::OutputBuffer& buf);
/// @brief Parses buffer.
///
/// Parses received buffer, returns offset to the first unused byte after
......@@ -150,7 +203,7 @@ public:
/// Returns pointer to actual data.
///
/// @return pointer to actual data (or NULL if there is no data)
virtual uint8_t*
virtual const std::vector<uint8_t>&
getData();
/// Adds a sub-option.
......@@ -242,26 +295,31 @@ protected:
unsigned int offset,
unsigned int parse_len);
/// @brief A private method used for option correctness.
///
/// 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();
/// option universe (V4 or V6)
Universe universe_;
/// option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
unsigned short type_;
/// shared pointer to a buffer (usually a part of packet)
boost::shared_array<uint8_t> data_;
/// length of data only. Use len() if you want to
/// know proper length with option header overhead
unsigned int data_len_;
/// contains content of this data
std::vector<uint8_t> data_;
/// TODO: Remove this field. vector<uint8_t> should be used
/// instead.
/// data is a shared_pointer that points out to the
/// whole packet. offset_ specifies where data for
/// this option begins.
unsigned int offset_;
/// collection for storing suboptions
Option6Collection options_;
OptionCollection options_;