Commit 21a15e9e authored by Marcin Siodelski's avatar Marcin Siodelski

[3360] Implemented the CSV lease file parser for DHCPv4.

parent 587f21a5
......@@ -49,24 +49,8 @@ DUID::DUID(const uint8_t* data, size_t len) {
duid_ = std::vector<uint8_t>(data, data + len);
}
const std::vector<uint8_t>& DUID::getDuid() const {
return (duid_);
}
DUID::DUIDType DUID::getType() const {
if (duid_.size() < 2) {
return (DUID_UNKNOWN);
}
uint16_t type = (duid_[0] << 8) + duid_[1];
if (type < DUID_MAX) {
return (static_cast<DUID::DUIDType>(type));
} else {
return (DUID_UNKNOWN);
}
}
DUID
DUID::fromText(const std::string& text) {
std::vector<uint8_t>
DUID::decode(const std::string& text) {
/// @todo optimize stream operations here.
std::vector<std::string> split_text;
boost::split(split_text, text, boost::is_any_of(":"),
......@@ -78,22 +62,41 @@ DUID::fromText(const std::string& text) {
s << "0";
} else if (split_text[i].size() > 2) {
isc_throw(isc::BadValue, "invalid DUID '" << text << "'");
isc_throw(isc::BadValue, "invalid identifier '" << text << "'");
}
s << split_text[i];
}
if (s.str().empty()) {
isc_throw(isc::BadValue, "empty DUID is not allowed");
}
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create DUID from text '"
isc_throw(isc::BadValue, "failed to create identifier from text '"
<< text << "': " << ex.what());
}
return DUID(&binary[0], binary.size());
return (binary);
}
const std::vector<uint8_t>& DUID::getDuid() const {
return (duid_);
}
DUID::DUIDType DUID::getType() const {
if (duid_.size() < 2) {
return (DUID_UNKNOWN);
}
uint16_t type = (duid_[0] << 8) + duid_[1];
if (type < DUID_MAX) {
return (static_cast<DUID::DUIDType>(type));
} else {
return (DUID_UNKNOWN);
}
}
DUID
DUID::fromText(const std::string& text) {
std::vector<uint8_t> binary = decode(text);
return DUID(binary);
}
std::string DUID::toText() const {
......@@ -152,6 +155,12 @@ std::string ClientId::toText() const {
return (DUID::toText());
}
ClientIdPtr
ClientId::fromText(const std::string& text) {
std::vector<uint8_t> binary = decode(text);
return (ClientIdPtr(new ClientId(binary)));
}
// Compares two client-ids
bool ClientId::operator==(const ClientId& other) const {
return (this->duid_ == other.duid_);
......
......@@ -72,13 +72,7 @@ class DUID {
/// @brief Create DUID from the textual format.
///
/// This static function parses a DUID specified in the textual format.
/// The format being parsed should match the DUID representation returned
/// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
/// representing bytes of DUID must be separated by colons. Usually the
/// single byte is represented by two hexadecimal digits. However, this
/// function allows one digit per byte. In this case, a zero is prepended
/// before the conversion. For example, a DUID 0:1:2::4:5 equals to
/// 00:01:02:00:04:05.
/// Internally it uses @c DUID::decode to parse the DUID.
///
/// @param text DUID in the hexadecimal format with digits representing
/// individual bytes separated by colons.
......@@ -96,6 +90,23 @@ class DUID {
bool operator!=(const DUID& other) const;
protected:
/// @brief Decodes the textual format of the DUID.
///
/// The format being parsed should match the DUID representation returned
/// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
/// representing bytes of DUID must be separated by colons. Usually the
/// single byte is represented by two hexadecimal digits. However, this
/// function allows one digit per byte. In this case, a zero is prepended
/// before the conversion. For example, a DUID 0:1:2::4:5 equals to
/// 00:01:02:00:04:05.
///
/// @param text DUID in the hexadecimal format with digits representing
/// individual bytes separated by colons.
///
/// @throw isc::BadValue if parsing the DUID failed.
static std::vector<uint8_t> decode(const std::string& text);
/// The actual content of the DUID
std::vector<uint8_t> duid_;
};
......@@ -103,7 +114,10 @@ class DUID {
/// @brief Shared pointer to a DUID
typedef boost::shared_ptr<DUID> DuidPtr;
/// @brief Forward declaration to the @c ClientId class.
class ClientId;
/// @brief Shared pointer to a Client ID.
typedef boost::shared_ptr<ClientId> ClientIdPtr;
/// @brief Holds Client identifier or client IPv4 address
///
......@@ -147,6 +161,19 @@ public:
/// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
std::string toText() const;
/// @brief Create client identifier from the textual format.
///
/// This static function creates the instance of the @c ClientId from the
/// textual format. Internally it calls @c DUID::fromText. The format of
/// the input must match the format of the DUID in @c DUID::fromText.
///
/// @param text Client identifier in the textual format.
///
/// @return Pointer to the instance of the @c ClientId.
/// @throw isc::BadValue if parsing the client identifier failed.
/// @throw isc::OutOfRange if the client identifier is truncated.
static ClientIdPtr fromText(const std::string& text);
/// @brief Compares two client-ids for equality
bool operator==(const ClientId& other) const;
......@@ -154,9 +181,6 @@ public:
bool operator!=(const ClientId& other) const;
};
/// @brief Shared pointer to a Client ID.
typedef boost::shared_ptr<ClientId> ClientIdPtr;
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2014 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
......@@ -15,6 +15,10 @@
#include <dhcp/hwaddr.h>
#include <dhcp/dhcp4.h>
#include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iomanip>
#include <sstream>
#include <vector>
......@@ -42,9 +46,11 @@ HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
}
}
std::string HWAddr::toText() const {
std::string HWAddr::toText(bool include_htype) const {
std::stringstream tmp;
tmp << "hwtype=" << static_cast<int>(htype_) << " ";
if (include_htype) {
tmp << "hwtype=" << static_cast<int>(htype_) << " ";
}
tmp << std::hex;
bool delim = false;
for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
......@@ -58,6 +64,34 @@ std::string HWAddr::toText() const {
return (tmp.str());
}
HWAddr
HWAddr::fromText(const std::string& text) {
/// @todo optimize stream operations here.
std::vector<std::string> split_text;
boost::split(split_text, text, boost::is_any_of(":"),
boost::algorithm::token_compress_on);
std::ostringstream s;
for (size_t i = 0; i < split_text.size(); ++i) {
if (split_text[i].size() == 1) {
s << "0";
} else if (split_text[i].size() > 2) {
isc_throw(isc::BadValue, "invalid hwaddr '" << text << "'");
}
s << split_text[i];
}
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create hwaddr from text '"
<< text << "': " << ex.what());
}
return (HWAddr(binary, HTYPE_ETHER));
}
bool HWAddr::operator==(const HWAddr& other) const {
return ((this->htype_ == other.htype_) &&
(this->hwaddr_ == other.hwaddr_));
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2014 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
......@@ -54,8 +54,34 @@ public:
// Hardware type
uint8_t htype_;
/// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
std::string toText() const;
/// @brief Returns textual representation of a hardware address
/// (e.g. 00:01:02:03:04:05)
///
/// @param include_htype Boolean value which controls whether the hardware
/// type is included in the returned string (true), or not (false).
///
/// @return Hardware address in the textual format.
std::string toText(bool include_htype = true) const;
/// @brief Creates instance of the hardware address from textual format.
///
/// This function parses HW address specified as text and creates the
/// corresponding @c HWAddr instance. The hexadecimal digits representing
/// individual bytes of the hardware address should be separated with
/// colons. Typically, two digits per byte are used. However, this function
/// allows for 1 digit per HW address byte. In this case, the digit is
/// prepended with '0' during conversion to binary value.
///
/// This function can be used to perform a reverse operation to the
/// @c HWAddr::toText(false).
///
/// The instance created by this function sets HTYPE_ETHER as a hardware
/// type.
///
/// @param text HW address in the textual format.
///
/// @return Instance of the HW address created from text.
static HWAddr fromText(const std::string& text);
/// @brief Compares two hardware addresses for equality
bool operator==(const HWAddr& other) const;
......
......@@ -280,4 +280,36 @@ TEST(ClientIdTest, toText) {
EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}
// This test checks that the ClientId instance can be created from the textual
// format and that error is reported if the textual format is invalid.
TEST(ClientIdTest, fromText) {
ClientIdPtr cid;
// ClientId with only decimal digits.
ASSERT_NO_THROW(
cid = ClientId::fromText("00:01:02:03:04:05:06")
);
EXPECT_EQ("00:01:02:03:04:05:06", cid->toText());
// ClientId with some hexadecimal digits (upper case and lower case).
ASSERT_NO_THROW(
cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab")
);
EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText());
// ClientId with one digit for a particular byte.
ASSERT_NO_THROW(
cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
);
EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
// Repeated colon sign in the ClientId should be ignored.
ASSERT_NO_THROW(
cid = ClientId::fromText("00::bb:D:ee:EF:ab")
);
EXPECT_EQ("00:bb:0d:ee:ef:ab", cid->toText());
// ClientId with excessive number of digits for one of the bytes.
EXPECT_THROW(
cid = ClientId::fromText("00:01:021:03:04:05:06"),
isc::BadValue
);
}
} // end of anonymous namespace
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012, 2014 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
......@@ -108,6 +108,9 @@ TEST(HWAddrTest, toText) {
EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
// In some cases we don't want htype value to be included. Check that
// it can be forced.
EXPECT_EQ("00:01:02:03:04:05", hw->toText(false));
}
TEST(HWAddrTest, stringConversion) {
......@@ -131,5 +134,27 @@ TEST(HWAddrTest, stringConversion) {
EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
}
// Checks that the HW address can be created from the textual format.
TEST(HWAddrTest, fromText) {
scoped_ptr<HWAddr> hwaddr;
// Create HWAddr from text.
ASSERT_NO_THROW(
hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67")));
);
EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false));
// HWAddr class should allow empty address.
ASSERT_NO_THROW(
hwaddr.reset(new HWAddr(HWAddr::fromText("")));
);
EXPECT_TRUE(hwaddr->toText(false).empty());
// There should be no more than two digits per byte of the HW addr.
EXPECT_THROW(
hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
isc::BadValue
);
}
} // end of anonymous namespace
......@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
libb10_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
libb10_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
libb10_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
libb10_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
......
// Copyright (C) 2014 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 <dhcpsrv/csv_lease_file4.h>
using namespace isc::asiolink;
using namespace isc::util;
namespace isc {
namespace dhcp {
CSVLeaseFile4::CSVLeaseFile4(const std::string& filename)
: CSVFile(filename) {
initColumns();
}
void
CSVLeaseFile4::append(const Lease4& lease) const {
CSVRow row(getColumnCount());
row.writeAt(getColumnIndex("address"), lease.addr_.toText());
HWAddr hwaddr(lease.hwaddr_, HTYPE_ETHER);
row.writeAt(getColumnIndex("hwaddr"), hwaddr.toText(false));
// Client id may be unset (NULL).
if (lease.client_id_) {
row.writeAt(getColumnIndex("client_id"), lease.client_id_->toText());
}
row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
row.writeAt(getColumnIndex("expire"), lease.cltt_ + lease.valid_lft_);
row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
row.writeAt(getColumnIndex("hostname"), lease.hostname_);
CSVFile::append(row);
}
bool
CSVLeaseFile4::next(Lease4Ptr& lease) {
// We will return NULL pointer if the lease is not read.
lease.reset();
// Get the row of CSV values.
CSVRow row;
CSVFile::next(row);
// The empty row signals EOF.
if (row == CSVFile::EMPTY_ROW()) {
return (true);
}
// Try to create a lease from the values read. This may easily result in
// exception. We don't want this function to throw exceptions, so we catch
// them all and rather return the false value.
try {
// Get client id. It is possible that the client id is empty and the
// returned pointer is NULL. This is ok, but if the client id is NULL,
// we need to be careful to not use the NULL pointer.
ClientIdPtr client_id = readClientId(row);
std::vector<uint8_t> client_id_vec;
if (client_id) {
client_id_vec = client_id->getClientId();
}
size_t client_id_len = client_id_vec.empty() ? 0 : client_id_vec.size();
// Get the HW address. It should never be empty and the readHWAddr checks
// that.
HWAddr hwaddr = readHWAddr(row);
lease.reset(new Lease4(readAddress(row),
&hwaddr.hwaddr_[0], hwaddr.hwaddr_.size(),
client_id_vec.empty() ? NULL : &client_id_vec[0],
client_id_len,
readValid(row),
0, 0, // t1, t2 = 0
readCltt(row),
readSubnetID(row),
readFqdnFwd(row),
readFqdnRev(row),
readHostname(row)));
} catch (std::exception& ex) {
// The lease might have been created, so let's set it back to NULL to
// signal that lease hasn't been parsed.
lease.reset();
setReadMsg(ex.what());
return (false);
}
return (true);
}
void
CSVLeaseFile4::initColumns() {
addColumn("address");
addColumn("hwaddr");
addColumn("client_id");
addColumn("valid_lifetime");
addColumn("expire");
addColumn("subnet_id");
addColumn("fqdn_fwd");
addColumn("fqdn_rev");
addColumn("hostname");
}
IOAddress
CSVLeaseFile4::readAddress(const CSVRow& row) {
IOAddress address(row.readAt(getColumnIndex("address")));
return (address);
}
HWAddr
CSVLeaseFile4::readHWAddr(const CSVRow& row) {
HWAddr hwaddr = HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")));
if (hwaddr.hwaddr_.empty()) {
isc_throw(isc::BadValue, "hardware address in the lease file"
" must not be empty");
}
return (hwaddr);
}
ClientIdPtr
CSVLeaseFile4::readClientId(const CSVRow& row) {
std::string client_id = row.readAt(getColumnIndex("client_id"));
// NULL client ids are allowed in DHCPv4.
if (client_id.empty()) {
return (ClientIdPtr());
}
ClientIdPtr cid = ClientId::fromText(client_id);
return (cid);
}
uint32_t
CSVLeaseFile4::readValid(const CSVRow& row) {
uint32_t valid =
row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
return (valid);
}
time_t
CSVLeaseFile4::readCltt(const CSVRow& row) {
uint32_t cltt = row.readAndConvertAt<uint32_t>(getColumnIndex("expire"))
- readValid(row);
return (cltt);
}
SubnetID
CSVLeaseFile4::readSubnetID(const CSVRow& row) {
SubnetID subnet_id =
row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
return (subnet_id);
}
bool
CSVLeaseFile4::readFqdnFwd(const CSVRow& row) {
bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
return (fqdn_fwd);
}
bool
CSVLeaseFile4::readFqdnRev(const CSVRow& row) {
bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
return (fqdn_rev);
}
std::string
CSVLeaseFile4::readHostname(const CSVRow& row) {
std::string hostname = row.readAt(getColumnIndex("hostname"));
return (hostname);
}
} // end of namespace isc::dhcp
} // end of namespace isc
// Copyright (C) 2014 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 CSV_LEASE_FILE4_H
#define CSV_LEASE_FILE4_H
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/subnet.h>
#include <util/csv_file.h>
#include <stdint.h>
#include <string>
#include <time.h>
namespace isc {
namespace dhcp {
/// @brief Provides methods to access CSV file with DHCPv4 leases.
///
/// This class contains methods customized to read DHCPv4 leases from the CSV
/// file. It expects that the CSV file being parsed, contains the set of columns
/// with well known names (initialized in the class constructor).
///
/// @todo This class doesn't validate the lease values read from the file.
/// The @c Lease4 is a structure that should be itself responsible for this
/// validation (see http://bind10.isc.org/ticket/2405). However, when #2405
/// is implemented, the @c next function may need to be updated to use the
/// validation capablity of @c Lease4.
class CSVLeaseFile4 : public isc::util::CSVFile {
public:
/// @brief Constructor.
///
/// Initializes columns of the lease file.
///
/// @param filename Name of the lease file.
CSVLeaseFile4(const std::string& filename);
/// @brief Appends the lease record to the CSV file.
///
/// This function doesn't throw exceptions itself. In theory, exceptions
/// are possible when the index of the indexes of the values being written
/// to the file are invalid. However, this would have been a programming
/// error.
///
/// @param lease Structure representing a DHCPv4 lease.
void append(const Lease4& lease) const;
/// @brief Reads next lease from the CSV file.
///
/// If this function hits an error during lease read, it sets the error
/// message using @c CSVFile::setReadMsg and returns false. The error
/// string may be read using @c CSVFile::getReadMsg.
///
/// This function is exception safe.
///
/// @param [out] lease Pointer to the lease read from CSV file or
/// NULL pointer if lease hasn't been read.
///
/// @return Boolean value indicating that the new lease has been
/// read from the CSV file (if true), or that the error has occurred
/// (false).
///
/// @todo Make sure that the values read from the file are correct.
/// The appropriate @c Lease4 validation mechanism should be used once
/// ticket http://bind10.isc.org/ticket/2405 is implemented.
bool next(Lease4Ptr& lease);
private:
/// @brief Initializes columns of the CSV file holding leases.
///
/// This function initializes the following columns:
/// - address
/// - hwaddr
/// - client_id
/// - valid_lifetime
/// - expire
/// - subnet_id
/// - fqdn_fwd
/// - fqdn_rev
/// - hostname
void initColumns();
///
/// @name Methods which read specific lease fields from the CSV row.
///
//@{
///
/// @brief Reads lease address from the CSV file row.
///
/// @param row CSV file holding lease values.
asiolink::IOAddress readAddress(const util::CSVRow& row);