Commit c5117dc4 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[1350] Added DHCPv4 option for storing single or list of IPv4 addresses.

Added Option4_AddrLst class that represents DHCPv4 option that stores
a single IPv4 address or a list of IPv4 addresses.
Added tests for this new option.
Added check in Option6_AddrLst, so it accepts only IPv6 addresses.
Removed unnecessary pack4() method in Option class.
parent 05d4deb6
......@@ -14,6 +14,7 @@ libdhcp_la_SOURCES += option.cc option.h
libdhcp_la_SOURCES += option6_ia.cc option6_ia.h
libdhcp_la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libdhcp_la_SOURCES += option6_addrlst.cc option6_addrlst.h
libdhcp_la_SOURCES += option4_addrlst.cc option4_addrlst.h
libdhcp_la_SOURCES += dhcp6.h dhcp4.h
libdhcp_la_SOURCES += pkt6.cc pkt6.h
libdhcp_la_SOURCES += pkt4.cc pkt4.h
......
......@@ -127,23 +127,6 @@ Option::pack4(isc::util::OutputBuffer& buf) {
}
}
unsigned int
Option::pack4(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset) {
if (offset + len() > buf_len) {
isc_throw(OutOfRange, "Failed to pack v4 option=" <<
type_ << ",len=" << len() << ": too small buffer.");
}
uint8_t *ptr = &buf[offset];
ptr[0] = type_;
ptr[1] = len() - getHeaderLen();
ptr += 2;
memcpy(ptr, &data_[0], data_.size());
return offset + len();
}
unsigned int
Option::pack6(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
......
......@@ -244,20 +244,6 @@ public:
~Option();
protected:
/// Builds raw (over-wire) buffer of this option, including all
/// defined suboptions. Version for building DHCPv4 options.
///
/// @param buf output buffer (built options will be stored here)
/// @param buf_len buffer length (used for buffer overflow checks)
/// @param offset offset from start of the buf buffer
///
/// @return offset to the next byte after last used byte
virtual unsigned int
pack4(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset);
/// Builds raw (over-wire) buffer of this option, including all
/// defined suboptions. Version for building DHCPv4 options.
///
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <sstream>
#include <iomanip>
#include <exceptions/exceptions.h>
#include <asiolink/io_address.h>
#include <util/io_utilities.h>
#include <dhcp/option4_addrlst.h>
using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::asiolink;
Option4AddrLst::Option4AddrLst(uint8_t type)
:Option(V4, type) {
}
Option4AddrLst::Option4AddrLst(uint8_t type, const AddressContainer& addrs)
:Option(V4, type) {
setAddresses(addrs);
// don't set addrs_ directly. setAddresses() will do additional checks.
}
Option4AddrLst::Option4AddrLst(uint8_t type,
vector<uint8_t>::const_iterator first,
vector<uint8_t>::const_iterator last)
:Option(V4, type) {
vector<uint8_t> buf = std::vector<uint8_t>(first, last);
if ( (buf.size() % V4ADDRESS_LEN) ) {
isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_
<< " has invalid length=" << buf.size()
<< ", must be divisible by 4.");
}
while (first != last) {
const uint8_t* ptr = &(*first);
addAddress(IOAddress(readUint32(ptr)));
first += V4ADDRESS_LEN;
}
}
Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
:Option(V4, type) {
setAddress(addr);
}
void
Option4AddrLst::pack4(isc::util::OutputBuffer& buf) {
if (addrs_.size() * V4ADDRESS_LEN > 255) {
isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << 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());
AddressContainer::const_iterator addr = addrs_.begin();
while (addr != addrs_.end()) {
buf.writeUint32(*addr);
addr++;
}
}
void Option4AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
if (addr.getFamily() != AF_INET) {
isc_throw(BadValue, "Can't store non-IPv4 address in "
<< "Option4AddrLst option");
}
addrs_.clear();
addAddress(addr);
}
void Option4AddrLst::setAddresses(const AddressContainer& addrs) {
// Do not copy it as a whole. addAddress() does sanity checks.
// i.e. throw if someone tries to set IPv6 address.
addrs_.clear();
for (AddressContainer::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
addAddress(*addr);
}
}
void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) {
if (addr.getFamily() != AF_INET) {
isc_throw(BadValue, "Can't store non-IPv4 address in "
<< "Option4AddrLst option");
}
addrs_.push_back(addr);
}
unsigned short
Option4AddrLst::len() {
// Returns length of the complete option (option header + data length)
return (getHeaderLen() + addrs_.size() * V4ADDRESS_LEN);
}
std::string Option4AddrLst::toText(int indent /* =0 */ ) {
std::stringstream tmp;
for (int i = 0; i < indent; i++)
tmp << " ";
tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ": ";
for (AddressContainer::const_iterator addr = addrs_.begin();
addr != addrs_.end(); ++addr) {
tmp << (*addr) << " ";
}
return tmp.str();
}
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef OPTION4_ADDRLST_H_
#define OPTION4_ADDRLST_H_
#include <string>
#include <map>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>
#include <util/buffer.h>
#include <dhcp/option.h>
namespace isc {
namespace dhcp {
/// @brief DHCPv4 Option class for handling list of IPv4 addresses.
///
/// This class handles a list of IPv4 addresses. An example of such option
/// is dns-servers option. It can also be used to handle a single address.
class Option4AddrLst : public isc::dhcp::Option {
public:
/// Defines a collection of IPv4 addresses.
typedef std::vector<isc::asiolink::IOAddress> AddressContainer;
/// @brief Constructor, creates an option with empty list of addresses.
///
/// Creates empty option that can hold addresses. Addresses can be added
/// with addAddress(), setAddress() or setAddresses().
///
/// @param type option type
Option4AddrLst(uint8_t type);
/// @brief Constructor, creates an option with a list of addresses.
///
/// Creates an option that contains specified list of IPv4 addresses.
///
/// @param type option type
/// @param addrs container with a list of addresses
Option4AddrLst(uint8_t type, const AddressContainer& addrs);
/// @brief Constrcutor, creates an option with a single address.
///
/// Creates an option that contains a single address.
///
/// @param type option type
/// @param addr a single address that will be stored as 1-elem. address list
Option4AddrLst(uint8_t type, const isc::asiolink::IOAddress& addr);
/// @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 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.
Option4AddrLst(uint8_t type, std::vector<uint8_t>::const_iterator first,
std::vector<uint8_t>::const_iterator last);
/// @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);
/// 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);
/// Returns length of the complete option (data length + DHCPv4/DHCPv6
/// option header)
///
/// @return length of the option
virtual unsigned short
len();
/// @brief Returns vector with addresses.
///
/// We return a copy of our list. Although this includes overhead,
/// it also makes this list safe to use after this option object
/// is no longer available. As options are expected to hold only
/// a couple (1-3) addresses, the overhead is not that big.
///
/// @return address container with addresses
AddressContainer
getAddresses() { return addrs_; };
/// @brief Sets addresses list.
///
/// Clears existing list of addresses and adds a single address to that
/// list. This is very convenient method for options that are supposed to
/// only a single option. See addAddress() if you want to add
/// address to existing list or setAddresses() if you want to
/// set the whole list at once.
///
/// Passed address must be IPv4 address. Otherwire BadValue exception
/// will be thrown.
///
/// @param addrs address collection to be set
void setAddresses(const AddressContainer& addrs);
/// @brief Clears address list and sets a single address.
///
/// Clears existing list of addresses and adds a single address to that
/// list. This is very convenient method for options that are supposed to
/// only a single option. See addAddress() if you want to add
/// address to existing list or setAddresses() if you want to
/// set the whole list at once.
///
/// Passed address must be IPv4 address. Otherwire BadValue exception
/// will be thrown.
///
/// @param addr an address that is going to be set as 1-element address list
void setAddress(const isc::asiolink::IOAddress& addr);
/// @brief Adds address to existing list of addresses.
///
/// Adds a single address to that list. See setAddress() if you want to
/// define only a single address or setAddresses() if you want to
/// set the whole list at once.
///
/// Passed address must be IPv4 address. Otherwire BadValue exception
/// will be thrown.
///
/// @param addr an address thait is going to be added to existing list
void addAddress(const isc::asiolink::IOAddress& addr);
protected:
/// contains list of addresses
AddressContainer addrs_;
};
} // namespace isc::dhcp
} // namespace isc
#endif
......@@ -50,6 +50,10 @@ Option6AddrLst::Option6AddrLst(unsigned short type,
void
Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
if (addr.getFamily() != AF_INET6) {
isc_throw(BadValue, "Can't store non-IPv6 address in Option6AddrLst option");
}
addrs_.clear();
addrs_.push_back(addr);
}
......
......@@ -16,17 +16,16 @@
#define OPTION6_ADDRLST_H_
#include <vector>
#include "asiolink/io_address.h"
#include "dhcp/option.h"
#include <asiolink/io_address.h>
#include <dhcp/option.h>
namespace isc {
namespace dhcp {
/// @brief Option class for handling list of IPv6 addresses.
/// @brief DHCPv6 Option class for handling list of IPv6 addresses.
///
/// This class handles a list of IPv6 addresses. An example of such option
/// is dns-servers option. It can also be used to handle single address.
///
class Option6AddrLst: public Option {
public:
......@@ -105,12 +104,12 @@ public:
/// @brief Returns vector with addresses.
///
/// As user may want to use/modify this list, it is better to return
/// a copy rather than const reference to the original. This is
/// usually one or two addresses long, so it is not a big deal.
///
/// @return vector with addresses
/// We return a copy of our list. Although this includes overhead,
/// it also makes this list safe to use after this option object
/// is no longer available. As options are expected to hold only
/// a couple (1-3) addresses, the overhead is not that big.
///
/// @return address container with addresses
AddressContainer
getAddresses() { return addrs_; };
......
......@@ -18,6 +18,7 @@ libdhcp_unittests_SOURCES += ../libdhcp.h ../libdhcp.cc libdhcp_unittest.cc
libdhcp_unittests_SOURCES += ../option6_iaaddr.h ../option6_iaaddr.cc option6_iaaddr_unittest.cc
libdhcp_unittests_SOURCES += ../option6_ia.h ../option6_ia.cc option6_ia_unittest.cc
libdhcp_unittests_SOURCES += ../option6_addrlst.h ../option6_addrlst.cc option6_addrlst_unittest.cc
libdhcp_unittests_SOURCES += ../option4_addrlst.cc ../option4_addrlst.h option4_addrlst_unittest.cc
libdhcp_unittests_SOURCES += ../option.h ../option.cc option_unittest.cc
libdhcp_unittests_SOURCES += ../pkt6.h ../pkt6.cc pkt6_unittest.cc
libdhcp_unittests_SOURCES += ../pkt4.h ../pkt4.cc pkt4_unittest.cc
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <iostream>
#include <sstream>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <util/buffer.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
namespace {
// a sample data (list of 4 addresses)
const uint8_t sampledata[] = {
192, 0, 2, 3, // 192.0.2.3
255, 255, 255, 0, // 255.255.255.0 - popular netmask
0, 0, 0 , 0, // used for default routes or (any address)
127, 0, 0, 1 // loopback
};
// expected on-wire format for an option with 1 address
const uint8_t expected1[] = { // 1 address
DHO_DOMAIN_NAME_SERVERS, 4, // type, length
192, 0, 2, 3, // 192.0.2.3
};
// expected on-wire format for an option with 4 addresses
const uint8_t expected4[] = { // 4 addresses
254, 16, // type = 254, len = 16
192, 0, 2, 3, // 192.0.2.3
255, 255, 255, 0, // 255.255.255.0 - popular netmask
0, 0, 0 ,0, // used for default routes or (any address)
127, 0, 0, 1 // loopback
};
class Option4AddrLstTest : public ::testing::Test {
protected:
Option4AddrLstTest():
vec_(vector<uint8_t>(300,0)) // 300 bytes long filled with 0s
{
sampleAddrs_.push_back(IOAddress("192.0.2.3"));
sampleAddrs_.push_back(IOAddress("255.255.255.0"));
sampleAddrs_.push_back(IOAddress("0.0.0.0"));
sampleAddrs_.push_back(IOAddress("127.0.0.1"));
}
vector<uint8_t> vec_;
Option4AddrLst::AddressContainer sampleAddrs_;
};
TEST_F(Option4AddrLstTest, parse1) {
memcpy(&vec_[0], sampledata, sizeof(sampledata));
// just one address
Option4AddrLst* opt1 = 0;
EXPECT_NO_THROW(
opt1 = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
vec_.begin(),
vec_.begin()+4);
// use just first address (4 bytes), not the whole
// sampledata
);
EXPECT_EQ(Option::V4, opt1->getUniverse());
EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt1->getType());
EXPECT_EQ(6, opt1->len()); // 2 (header) + 4 (1x IPv4 addr)
Option4AddrLst::AddressContainer addrs = opt1->getAddresses();
ASSERT_EQ(1, addrs.size());
EXPECT_EQ("192.0.2.3", addrs[0].toText());
EXPECT_NO_THROW(
delete opt1;
opt1 = 0;
);
// 1 address
}
TEST_F(Option4AddrLstTest, parse4) {
vector<uint8_t> buffer(300,0); // 300 bytes long filled with 0s
memcpy(&buffer[0], sampledata, sizeof(sampledata));
// 4 addresses
Option4AddrLst* opt4 = 0;
EXPECT_NO_THROW(
opt4 = new Option4AddrLst(254,
buffer.begin(),
buffer.begin()+sizeof(sampledata));
);
EXPECT_EQ(Option::V4, opt4->getUniverse());
EXPECT_EQ(254, opt4->getType());
EXPECT_EQ(18, opt4->len()); // 2 (header) + 16 (4x IPv4 addrs)
Option4AddrLst::AddressContainer addrs = opt4->getAddresses();
ASSERT_EQ(4, addrs.size());
EXPECT_EQ("192.0.2.3", addrs[0].toText());
EXPECT_EQ("255.255.255.0", addrs[1].toText());
EXPECT_EQ("0.0.0.0", addrs[2].toText());
EXPECT_EQ("127.0.0.1", addrs[3].toText());
EXPECT_NO_THROW(
delete opt4;
opt4 = 0;
);
}
TEST_F(Option4AddrLstTest, assembly1) {
Option4AddrLst* opt = 0;
EXPECT_NO_THROW(
opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("192.0.2.3"));
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
Option4AddrLst::AddressContainer addrs = opt->getAddresses();
ASSERT_EQ(1, addrs.size() );
EXPECT_EQ("192.0.2.3", addrs[0].toText());
OutputBuffer buf(100);
EXPECT_NO_THROW(
opt->pack4(buf);
);
ASSERT_EQ(6, opt->len());
ASSERT_EQ(6, buf.getLength());
EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
EXPECT_NO_THROW(
delete opt;
opt = 0;
);
// This is old-fashioned option. We don't serve IPv6 types here!
EXPECT_THROW(
opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("2001:db8::1")),
BadValue
);
if (opt) {
// test failed. Execption was not thrown, but option was created instead.
delete opt;
}
}
TEST_F(Option4AddrLstTest, assembly4) {
Option4AddrLst* opt = 0;
EXPECT_NO_THROW(
opt = new Option4AddrLst(254, sampleAddrs_);
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(254, opt->getType());
Option4AddrLst::AddressContainer addrs = opt->getAddresses();
ASSERT_EQ(4, addrs.size() );
EXPECT_EQ("192.0.2.3", addrs[0].toText());
EXPECT_EQ("255.255.255.0", addrs[1].toText());
EXPECT_EQ("0.0.0.0", addrs[2].toText());