Commit 7cf66b7e authored by Stephen Morris's avatar Stephen Morris
Browse files

[trac998] Just the undefined abstract methods to worry about

parent 0c3b69c6
......@@ -16,10 +16,12 @@
#define __IP_CHECK_H
#include <cassert>
#include <iterator>
#include <utility>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <stdint.h>
#include <arpa/inet.h>
......@@ -136,6 +138,27 @@ splitIpAddress(const std::string& addrmask, uint32_t maxmask) {
}
/// \brief IP Base
///
/// Base class for both the Ipv4 and Ipv4 check classes. The only thing this
/// supplies over and above the Check class is a clone() method, to return
/// a pointer to a newly-allocated copy of itself.
template <typename Context>
class IpBaseCheck : public Check<Context> {
public:
/// \brief Clone Object
///
/// Implemented by derived classes, this causes a class to create a copy
/// of itself and return a pointer to the object.
///
/// \return Pointer to the cloned object. It is the caller's responsibility
/// to delete this object.
virtual IpBaseCheck* clone() const = 0;
};
/// \brief IPV4 Check
///
/// This class performs a match between an IPv4 address specified in an ACL
......@@ -145,7 +168,7 @@ splitIpAddress(const std::string& addrmask, uint32_t maxmask) {
/// \param Context Structure holding address to be matched.
template <typename Context>
class Ipv4Check : public Check<Context> {
class Ipv4Check : public IpBaseCheck<Context> {
public:
/// \brief IPV4 Constructor
///
......@@ -167,8 +190,6 @@ public:
setNetmask();
}
/// \brief String Constructor
///
/// Constructs an IPv4 Check object from a network address and size of mask
......@@ -199,13 +220,12 @@ public:
setNetmask();
}
// Default copy constructor and assignment operator are correct for this
// class.
/// \brief Destructor
virtual ~Ipv4Check() {}
/// \brief The check itself
///
/// Matches the passed argument to the condition stored here. Different
......@@ -215,8 +235,6 @@ public:
/// \param context Information to be matched
virtual bool matches(const Context& context) const = 0;
/// \brief Estimated cost
///
/// Assume that the cost of the match is linear and depends on the number
......@@ -225,8 +243,6 @@ public:
return (1); // Single check on a 32-bit word
}
///@{
/// Access methods - mainly for testing
......@@ -310,7 +326,6 @@ public:
size_t masksize_; ///< Mask size passed to constructor
bool inverse_; ///< Test for equality or inequality
uint32_t netmask_; ///< Network mask applied to match
};
......@@ -331,12 +346,18 @@ bool isNonZero(uint8_t i) {
/// \param Context Structure holding address to be matched.
template <typename Context>
class Ipv6Check : public Check<Context> {
class Ipv6Check : public IpBaseCheck<Context> {
public:
// Size of array to old IPV6 address.
static const size_t IPV6_SIZE = sizeof(struct in6_addr);
/// \brief Default Constructor
Ipv6Check() :
address_(IPV6_SIZE, 0), masksize_(0),
inverse_(false), netmask_(IPV6_SIZE, 0)
{}
/// \brief IPV6 Constructor
///
/// Constructs an IPv6 Check object from a network address given as a
......@@ -354,27 +375,25 @@ public:
Ipv6Check(const uint8_t* address, size_t masksize = 8 * IPV6_SIZE,
bool inverse = false) :
address_(address, address + IPV6_SIZE), masksize_(masksize),
inverse_(inverse), netmask_(IPV6_SIZE, 0), netcount_(0)
inverse_(inverse), netmask_(IPV6_SIZE, 0)
{
setNetmask();
}
/// \brief String Constructor
///
/// Constructs an IPv6 Check object from a network address and size of mask
/// given as a string of the form "a.b.c.d/n", where the "/n" part is
/// optional.
///
/// \param address IP address and netmask in the form "<v6-address>/n" (where
/// the "/n" part is optional).
/// \param address IP address and netmask in the form "<v6-address>/n"
/// (where the "/n" part is optional).
/// \param inverse If false (the default), matches() returns true if the
/// condition matches. If true, matches() returns true if the
/// condition does not match.
Ipv6Check(const std::string& address, bool inverse = false) :
address_(IPV6_SIZE, 0), masksize_(8 * IPV6_SIZE),
inverse_(inverse), netmask_(IPV6_SIZE, 0), netcount_(0)
inverse_(inverse), netmask_(IPV6_SIZE, 0)
{
// Split the address into address part and mask.
std::pair<std::string, uint32_t> result =
......@@ -392,13 +411,35 @@ public:
setNetmask();
}
/// \brief Copy constructor
Ipv6Check(const Ipv6Check& other) : IpBaseCheck<Context>(other),
address_(other.address_.begin(), other.address_.end()),
masksize_(other.masksize_), inverse_(other.inverse_),
netmask_(other.netmask_.begin(), other.netmask_.end())
{}
/// \brief Assignment operator
Ipv6Check& operator=(const Ipv6Check& other) {
if (this != &other) {
// Copy parent
IpBaseCheck<Context>::operator=(other);
// Copy this object
address_.clear();
std::copy(other.address_.begin(), other.address_.end(),
std::back_inserter(address_));
masksize_ = other.masksize_;
inverse_ = other.inverse_;
netmask_.clear();
std::copy(other.netmask_.begin(), other.netmask_.end(),
std::back_inserter(netmask_));
}
return (*this);
}
/// \brief Destructor
virtual ~Ipv6Check() {}
/// \brief The check itself
///
/// Matches the passed argument to the condition stored here. Different
......@@ -408,8 +449,6 @@ public:
/// \param context Information to be matched
virtual bool matches(const Context& context) const = 0;
/// \brief Estimated cost
///
/// Assume that the cost of the match is linear and depends on the number
......@@ -418,8 +457,6 @@ public:
return (IPV6_SIZE); // Up to 16 checks
}
///@{
/// Access methods - mainly for testing
......@@ -456,24 +493,21 @@ public:
// Loop, setting the bits in the set of mask bytes until all the
// specified bits have been used up. The netmask array was
// initialized to zero in the constructor.
assert(netcount_ == 0); // Set in constructor
assert(std::find_if(netmask_.begin(), netmask_.end(), isNonZero) ==
netmask_.end());
size_t bits_left = masksize_; // Bits remaining to set
int i = -1; // To allow pre-incrementing
while (bits_left > 0) {
if (bits_left >= 8) {
netmask_[netcount_] = ~0; // All bits set
netmask_[++i] = ~0; // All bits set
bits_left -= 8;
} else if (bits_left > 0) {
netmask_[netcount_] = createNetmask<uint8_t>(bits_left);
netmask_[++i] = createNetmask<uint8_t>(bits_left);
bits_left = 0;
}
// One more byte to test against
++netcount_;
}
} else {
isc_throw(isc::OutOfRange,
......@@ -489,12 +523,12 @@ public:
/// to this class with the matching information in the class itself. It is
/// expected to be called from matches().
///
/// \param address Address (in network byte order) to match against the
/// check condition in the class. This is expected to
/// be IPV6_SIZE bytes long.
/// \param testaddr Address (in network byte order) to test against the
/// check condition in the class. This is expected to
/// be IPV6_SIZE bytes long.
///
/// \return true if the address matches, false if it does not.
virtual bool compare(const uint8_t* address) const {
virtual bool compare(const uint8_t* testaddr) const {
// To check that the address given matches the stored network address
// and netmask, we check the simple condition that:
......@@ -502,10 +536,16 @@ public:
// address_given & netmask_ == stored_address & netmask_
//
// The result is checked for all bytes for which there are bits set in
// the network mask. We stop at the first non-match.
// the network mask. We stop at the first non-match (or when we run
// out of bits in the network mask). (Note that the network mask
// represents a contiguous set of bits. As such, as soon as we find
// a netmask byte of zeroes, we have run part the part of the address
// where we need to match.
bool match = true;
for (size_t i = 0; (i < netcount_) && match; ++i) {
match = ((address[i] & netmask_[i]) == (address_[i] & netmask_[i]));
for (int i = 0; match && (i < IPV6_SIZE) && (netmask_[i] != 0); ++i){
match = ((testaddr[i] & netmask_[i]) ==
(address_[i] & netmask_[i]));
}
// As with the V4 check, return the XOR with the inverse flag.
......@@ -518,10 +558,90 @@ public:
size_t masksize_; ///< Mask size passed to constructor
bool inverse_; ///< Test for equality or inequality
std::vector<uint8_t> netmask_; ///< Network mask applied to match
size_t netcount_; ///< Number of bytes in mask to test
};
/// \brief Generic IP Address Check Object
///
/// This class performs a match between an IP address specified in an ACL
/// (IP address (either V4 of V6), network mask and a flag indicating whether
/// the check should be for a match or for a non-match) and a given IP address.
///
/// \param Context Structure holding address to be matched.
template <typename Context>
class IpCheck : public IpBaseCheck<Context> {
public:
/// \brief String Constructor
///
/// Constructs an IP Check object from a network address and size of mask
/// given as a string of the form "<address>/n", where the "/n" part is
/// optional.
///
/// \param address IP address and netmask in the form "<address>/n"
/// (where the "/n" part is optional).
/// \param inverse If false (the default), matches() returns true if the
/// condition matches. If true, matches() returns true if the
/// condition does not match.
IpCheck(const std::string& address, bool inverse = false) {
try {
check_.reset(new Ipv4Check<Context>(address, inverse));
isv4_ = true;
} catch (isc::Exception&) {
check_.reset(new Ipv6Check<Context>(address, inverse));
isv4_ = false;
}
}
/// \brief Copy constructor
IpCheck(const IpCheck<Context>& other) : IpBaseCheck<Context>(other),
check_(other.check_->clone())
{}
/// \brief Assignment operator
IpCheck& operator=(const IpCheck& other) {
if (this != &other) {
IpBaseCheck<Context>::operator=(other);
check_.reset(other.check_->clone());
}
}
/// \brief Clone
IpBaseCheck<Context>* clone() const {
return (new IpCheck<Context>(*this));
}
/// \brief Destructor
virtual ~IpCheck() {}
/// \brief The check itself
///
/// Matches the passed argument to the condition stored here. Different
/// specialisations are provided for different argument types, so the
/// link will fail if used for a type for which no match is provided.
///
/// \param context Information to be matched
virtual bool matches(const Context& context) const {
return (check_->matches(context));
}
/// \brief Estimated cost
///
/// Assume that the cost of the match is linear and depends on the number
/// of comparison operations.
virtual unsigned cost() const {
return (check_->cost());
}
private:
// Member variables
bool isv4_; ///< true if V4 address
boost::scoped_ptr<IpBaseCheck<Context> > check_; ///< Check object
};
} // namespace acl
} // namespace isc
......
......@@ -15,16 +15,70 @@
#include <gtest/gtest.h>
#include <acl/ip_check.h>
#include <boost/scoped_ptr.hpp>
using namespace isc::acl;
using namespace std;
// Declare a derived class to allow the abstract function to be declared
// as a concrete one.
/// *** Free Function Tests ***
TEST(IpFunctionCheck, CreateNetmask) {
// 8-bit tests: invalid arguments should throw.
EXPECT_THROW(createNetmask<uint8_t>(9), isc::OutOfRange);
// Check on all possible 8-bit values. Use a signed type to generate a
// variable with the most significant bits set, as right-shifting it is
// guaranteed to introduce additional bits.
int8_t expected8 = 0x80;
for (size_t i = 1; i <= 8; ++i, expected8 >>= 1) {
EXPECT_EQ(static_cast<uint8_t>(expected8), createNetmask<uint8_t>(i));
}
EXPECT_EQ(0, createNetmask<uint8_t>(0));
// Do the same for 32 bits.
EXPECT_THROW(createNetmask<int32_t>(33), isc::OutOfRange);
// Check on all possible 8-bit values
int32_t expected32 = 0x80000000;
for (size_t i = 1; i <= 32; ++i, expected32 >>= 1) {
EXPECT_EQ(static_cast<uint32_t>(expected32),
createNetmask<uint32_t>(i));
}
EXPECT_EQ(0, createNetmask<uint32_t>(0));
}
TEST(IpFunctionCheck, SplitIp) {
pair<string, uint32_t> result;
result = splitIpAddress("192.0.2.1/24", 32);
EXPECT_EQ(string("192.0.2.1"), result.first);
EXPECT_EQ(24, result.second);
result = splitIpAddress("192.0.2.1/32", 32);
EXPECT_EQ(string("192.0.2.1"), result.first);
EXPECT_EQ(32, result.second);
result = splitIpAddress("2001:db8::/128", 128);
EXPECT_EQ(string("2001:db8::"), result.first);
EXPECT_EQ(128, result.second);
EXPECT_THROW(splitIpAddress("2001:db8::/129", 128), isc::OutOfRange);
EXPECT_THROW(splitIpAddress("2001:db8::/xxxx", 32), isc::InvalidParameter);
EXPECT_THROW(splitIpAddress("2001:db8::/32/s", 32), isc::InvalidParameter);
}
// *** IPV4 Tests ***
// Declare a derived class to allow a definition of the match() method to be
// provided.
//
// In this case, the match will check a uint32_t variable representing an IPV4
// address.
class DerivedV4Check : public Ipv4Check<uint32_t> {
public:
// Basic constructor
// Basic (and default) constructor
DerivedV4Check(uint32_t address = 1, size_t masksize = 32,
bool inverse = false) :
Ipv4Check<uint32_t>(address, masksize, inverse)
......@@ -35,6 +89,23 @@ public:
Ipv4Check<uint32_t>(address, inverse)
{}
// Copy constructor
DerivedV4Check(const DerivedV4Check& other) : Ipv4Check<uint32_t>(other)
{}
// Assignment operator
DerivedV4Check& operator=(const DerivedV4Check& other) {
if (this != &other) {
Ipv4Check<uint32_t>::operator=(other);
}
return (*this);
}
// Clone method
virtual IpBaseCheck<uint32_t>* clone() const {
return (new DerivedV4Check(*this));
}
// Destructor
virtual ~DerivedV4Check()
{}
......@@ -45,54 +116,31 @@ public:
}
};
// Check that a default constructor can be instantiated.
/// Tests of the free functions.
TEST(Ipv4Check, CreateNetmask) {
size_t i;
// 8-bit tests.
// Invalid arguments should throw.
EXPECT_THROW(createNetmask<uint8_t>(9), isc::OutOfRange);
// Check on all possible 8-bit values. Use a signed type to generate a
// variable with the most significant bits set, as right-shifting it is
// guaranteed to introduce additional bits.
int8_t expected8;
for (i = 1, expected8 = 0x80; i <= 8; ++i, expected8 >>= 1) {
EXPECT_EQ(static_cast<uint8_t>(expected8),
createNetmask<uint8_t>(i));
}
EXPECT_EQ(0, createNetmask<uint8_t>(0));
TEST(Ipv4Check, V4DefaultConstructor) {
DerivedV4Check acl1;
// Do the same for 32 bits.
EXPECT_THROW(createNetmask<int32_t>(33), isc::OutOfRange);
// Check on all possible 8-bit values
int32_t expected32;
for (i = 1, expected32 = 0x80000000; i <= 32; ++i, expected32 >>= 1) {
EXPECT_EQ(static_cast<uint32_t>(expected32),
createNetmask<uint32_t>(i));
}
EXPECT_EQ(0, createNetmask<uint32_t>(0));
// The test is needed to avoid the unused variable causing a warning or
// getting optimised away.
EXPECT_EQ(1, acl1.getAddress());
}
// IPV4 tests
// Check that the constructor expands the network mask and stores the elements
// correctly. For these tests, we don't worry about the type of the context,
// so we declare it as an int.
// Check that the constructor stores the elements correctly. As the address
// is provided in the correct (network-byte order) format, the real test is
// checking that the network mask is stored in the correct byte order.
TEST(Ipv4Check, V4ConstructorAddress) {
DerivedV4Check acl1(0x12345678);
EXPECT_EQ(0x12345678, acl1.getAddress());
}
// The mask is stored in network byte order, so the pattern expected must
// also be converted to network byte order for the comparison to succeed.
TEST(Ipv4Check, V4ConstructorMask) {
// Valid values. Address of "1" is used as a placeholder
DerivedV4Check acl1(1, 1);
// The mask is stored in network byte order, so the pattern expected must
// also be converted to network byte order for the comparison to succeed.
uint32_t expected = htonl(0x80000000);
EXPECT_EQ(expected, acl1.getNetmask());
EXPECT_EQ(1, acl1.getMasksize());
......@@ -120,29 +168,61 @@ TEST(Ipv4Check, V4ConstructorInverse) {
}
TEST(Ipv4Check, V4StringConstructor) {
DerivedV4Check acl1("127.0.0.1");
uint32_t expected = htonl(0x7f000001);
DerivedV4Check acl1("192.168.132.255");
uint32_t expected = htonl(0xc0a884ff);
EXPECT_EQ(expected, acl1.getAddress());
EXPECT_EQ(32, acl1.getMasksize());
DerivedV4Check acl2("255.255.255.0/24");
expected = htonl(0xffffff00);
DerivedV4Check acl2("192.0.2.0/24");
expected = htonl(0xc0000200);
EXPECT_EQ(expected, acl2.getAddress());
EXPECT_EQ(24, acl2.getMasksize());
EXPECT_THROW(DerivedV4Check("255.255.255.0/0"), isc::OutOfRange);
EXPECT_THROW(DerivedV4Check("255.255.255.0/33"), isc::OutOfRange);
EXPECT_THROW(DerivedV4Check("255.255.255.0/24/3"), isc::InvalidParameter);
EXPECT_THROW(DerivedV4Check("255.255.255.0/ww"), isc::InvalidParameter);
EXPECT_THROW(DerivedV4Check("192.0.2.0/0"), isc::OutOfRange);
EXPECT_THROW(DerivedV4Check("192.0.2.0/33"), isc::OutOfRange);
EXPECT_THROW(DerivedV4Check("192.0.2.0/24/3"), isc::InvalidParameter);
EXPECT_THROW(DerivedV4Check("192.0.2.0/ww"), isc::InvalidParameter);
EXPECT_THROW(DerivedV4Check("aa.255.255.0/ww"), isc::InvalidParameter);
}
TEST(Ipv4Check, V4CopyConstructor) {
DerivedV4Check acl1("127.0.0.1/16", true);
DerivedV4Check acl2(acl1);
EXPECT_EQ(acl1.getAddress(), acl2.getAddress());
EXPECT_EQ(acl1.getMasksize(), acl2.getMasksize());
EXPECT_EQ(acl1.getNetmask(), acl2.getNetmask());
EXPECT_EQ(acl1.getInverse(), acl2.getInverse());
}
TEST(Ipv4Check, V4AssignmentOperator) {
DerivedV4Check acl1("127.0.0.1/16", true);
DerivedV4Check acl2("192.1.2.3/12", false);
acl2 = acl1;
EXPECT_EQ(acl1.getAddress(), acl2.getAddress());
EXPECT_EQ(acl1.getMasksize(), acl2.getMasksize());
EXPECT_EQ(acl1.getNetmask(), acl2.getNetmask());
EXPECT_EQ(acl1.getInverse(), acl2.getInverse());
}
TEST(IPv4Check, V4Clone) {
DerivedV4Check acl1("127.0.0.1/16", true);
boost::scoped_ptr<DerivedV4Check> acl2(
static_cast<DerivedV4Check*>(acl1.clone()));
EXPECT_EQ(acl1.getAddress(), acl2->getAddress());
EXPECT_EQ(acl1.getMasksize(), acl2->getMasksize());
EXPECT_EQ(acl1.getNetmask(), acl2->getNetmask());
EXPECT_EQ(acl1.getInverse(), acl2->getInverse());
}
// Check that the comparison works - note that "matches" just calls the
// internal compare() code.
//
// Note that addresses passed to the class are expected to be in network-
// byte order. Therefore for the comparisons to work as expected, we must
// convert the values to network-byte order first.
// As before, note that addresses passed to the class are expected to be in
// network-byte order. Therefore for the comparisons to work as expected, we
// must convert the values to network-byte order first.
TEST(Ipv4Check, V4Compare) {
// Exact address - match if given address matches stored address.
......@@ -172,17 +252,24 @@ TEST(Ipv4Check, V4Compare) {
EXPECT_FALSE(acl4.matches(htonl(0x2345ffff)));
EXPECT_TRUE(acl4.matches(htonl(0x23460000)));
EXPECT_TRUE(acl4.matches(htonl(0x2346ffff)));
}
// IPV6 tests
// Declare a derived class to allow the abstract function to be declared
// as a concrete one.
// *** IPV6 Tests ***
//
// Declare a derived class to allow a definition of the match() method to be
// provided.
//
// In this case, the match will check a vector of uint8_t s representing an IPV6
// address.
class DerivedV6Check : public Ipv6Check<vector<uint8_t> > {
public:
// default constructor
DerivedV6Check() : Ipv6Check<vector<uint8_t> >()
{}
// Basic constructor
DerivedV6Check(const uint8_t* address, size_t masksize = 128,
bool inverse = false) :
......@@ -194,6 +281,11 @@ public: