Commit 0c3b69c6 authored by Stephen Morris's avatar Stephen Morris
Browse files

[trac998] IPv6 checks working

parent f5edd310
......@@ -15,10 +15,12 @@
#ifndef __IP_CHECK_H
#define __IP_CHECK_H
#include <boost/lexical_cast.hpp>
#include <cassert>
#include <utility>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <stdint.h>
#include <arpa/inet.h>
#include <netinet/in.h>
......@@ -41,7 +43,7 @@ namespace acl {
///
/// The function is templated on the data type of the mask.
///
/// \param masksize Size of the mask. This must be between 1 and 8*sizeof(T).
/// \param masksize Size of the mask. This must be between 0 and 8*sizeof(T).
/// An out of range exception is thrown if this is not the case.
///
/// \return Value with the most significant "masksize" bits set.
......@@ -69,9 +71,16 @@ T createNetmask(size_t masksize) {
// the expression below will overflow.
return (~((1 << (8 * sizeof(T) - masksize)) - 1));
} else if (masksize == 0) {
// Simplifies logic elsewhere we we are allowed to cope with a mask
// size of 0.
return (0);
}
isc_throw(isc::OutOfRange, "mask size must be between 1 and " <<
isc_throw(isc::OutOfRange, "mask size must be between 0 and " <<
8 * sizeof(T));
}
......@@ -87,16 +96,16 @@ T createNetmask(size_t masksize) {
///
/// \param addrmask Address and/or address/mask. The string should be passed
/// without leading or trailing spaces.
/// \param defmask Default value of the mask size, used if no mask is given.
/// \param maxmask Maximum valid value of the mask size.
/// \param maxmask Default value of the mask size, used if no mask is given.
/// It is also the maximum permitted value of the mask.
///
/// \return Pair of (string, uint32) holding the address string and the mask
/// size value.
std::pair<std::string, uint32_t>
splitIpAddress(const std::string& addrmask, uint32_t defmask, uint32_t maxmask){
splitIpAddress(const std::string& addrmask, uint32_t maxmask) {
uint32_t masksize = defmask;
uint32_t masksize = maxmask;
// See if a mask size was given
std::vector<std::string> components = isc::util::str::tokens(addrmask, "/");
......@@ -176,7 +185,7 @@ public:
{
// Split the address into address part and mask.
std::pair<std::string, uint32_t> result =
splitIpAddress(address, 8 * sizeof(uint32_t), 8 * sizeof(uint32_t));
splitIpAddress(address, 8 * sizeof(uint32_t));
// Try to convert the address.
int status = inet_pton(AF_INET, result.first.c_str(), &address_);
......@@ -299,11 +308,220 @@ public:
uint32_t address_; ///< IPv4 address
size_t masksize_; ///< Mask size passed to constructor
bool inverse_; ///< test for equality or inequality
bool inverse_; ///< Test for equality or inequality
uint32_t netmask_; ///< Network mask applied to match
};
// Used for an assertion check
namespace {
bool isNonZero(uint8_t i) {
return (i != 0);
}
} // Anonymous namespace
/// \brief IPV6 Check
///
/// This class performs a match between an IPv6 address specified in an ACL
/// (IP address, 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 Ipv6Check : public Check<Context> {
public:
// Size of array to old IPV6 address.
static const size_t IPV6_SIZE = sizeof(struct in6_addr);
/// \brief IPV6 Constructor
///
/// Constructs an IPv6 Check object from a network address given as a
/// 16-byte array in network-byte order.
///
/// \param address IP address to check for (as an address in network-byte
/// order).
/// \param mask The network mask specified as an integer between 1 and
/// 128 This determines the number of bits in the mask to check.
/// An exception will be thrown if the number is not within these
/// bounds.
/// \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 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)
{
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 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)
{
// Split the address into address part and mask.
std::pair<std::string, uint32_t> result =
splitIpAddress(address, 8 * IPV6_SIZE);
// Try to convert the address.
int status = inet_pton(AF_INET6, result.first.c_str(), &address_[0]);
if (status == 0) {
isc_throw(isc::InvalidParameter, "address/masksize of " <<
address << " is not valid IPV6 address");
}
// All done, so finish initialization.
masksize_ = result.second;
setNetmask();
}
/// \brief Destructor
virtual ~Ipv6Check() {}
/// \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 = 0;
/// \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 (IPV6_SIZE); // Up to 16 checks
}
///@{
/// Access methods - mainly for testing
/// \return Stored IP address
std::vector<uint8_t> getAddress() const {
return (address_);
}
/// \return Network mask applied to match
std::vector<uint8_t> getNetmask() const {
return (netmask_);
}
/// \return Mask size given to constructor
size_t getMasksize() const {
return (masksize_);
}
/// \return Setting of inverse flag
bool getInverse() {
return (inverse_);
}
///@}
/// \brief Set Network Mask
///
/// Sets up the network mask from the mask size. This involves setting
/// mask in each byte of the network mask.
void setNetmask() {
// Validate that the mask is valid.
if ((masksize_ >= 1) && (masksize_ <= 8 * IPV6_SIZE)) {
// 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
while (bits_left > 0) {
if (bits_left >= 8) {
netmask_[netcount_] = ~0; // All bits set
bits_left -= 8;
} else if (bits_left > 0) {
netmask_[netcount_] = createNetmask<uint8_t>(bits_left);
bits_left = 0;
}
// One more byte to test against
++netcount_;
}
} else {
isc_throw(isc::OutOfRange,
"mask size of " << masksize_ << " is invalid " <<
"for the data type which is " << sizeof(uint32_t) <<
" bytes long");
}
}
/// \brief Comparison
///
/// This is the actual comparison function that checks the IP address passed
/// 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.
///
/// \return true if the address matches, false if it does not.
virtual bool compare(const uint8_t* address) const {
// To check that the address given matches the stored network address
// and netmask, we check the simple condition that:
//
// 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.
bool match = true;
for (size_t i = 0; (i < netcount_) && match; ++i) {
match = ((address[i] & netmask_[i]) == (address_[i] & netmask_[i]));
}
// As with the V4 check, return the XOR with the inverse flag.
return (match != inverse_);
}
// Member variables
std::vector<uint8_t> address_; ///< IPv6 address
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
};
} // namespace acl
} // namespace isc
......
......@@ -17,6 +17,7 @@
#include <acl/ip_check.h>
using namespace isc::acl;
using namespace std;
// Declare a derived class to allow the abstract function to be declared
// as a concrete one.
......@@ -30,7 +31,7 @@ public:
{}
// String constructor
DerivedV4Check(const std::string& address, bool inverse = false) :
DerivedV4Check(const string& address, bool inverse = false) :
Ipv4Check<uint32_t>(address, inverse)
{}
......@@ -47,13 +48,12 @@ public:
/// Tests of the free functions.
TEST(IpCheck, CreateNetmask) {
TEST(Ipv4Check, CreateNetmask) {
size_t i;
// 8-bit tests.
// Invalid arguments should throw.
EXPECT_THROW(createNetmask<uint8_t>(0), isc::OutOfRange);
EXPECT_THROW(createNetmask<uint8_t>(9), isc::OutOfRange);
// Check on all possible 8-bit values. Use a signed type to generate a
......@@ -64,9 +64,9 @@ TEST(IpCheck, CreateNetmask) {
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>(0), isc::OutOfRange);
EXPECT_THROW(createNetmask<int32_t>(33), isc::OutOfRange);
// Check on all possible 8-bit values
......@@ -75,6 +75,7 @@ TEST(IpCheck, CreateNetmask) {
EXPECT_EQ(static_cast<uint32_t>(expected32),
createNetmask<uint32_t>(i));
}
EXPECT_EQ(0, createNetmask<uint32_t>(0));
}
// IPV4 tests
......@@ -82,14 +83,14 @@ TEST(IpCheck, CreateNetmask) {
// correctly. For these tests, we don't worry about the type of the context,
// so we declare it as an int.
TEST(IpCheck, V4ConstructorAddress) {
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(IpCheck, V4ConstructorMask) {
TEST(Ipv4Check, V4ConstructorMask) {
// Valid values. Address of "1" is used as a placeholder
DerivedV4Check acl1(1, 1);
uint32_t expected = htonl(0x80000000);
......@@ -106,7 +107,7 @@ TEST(IpCheck, V4ConstructorMask) {
EXPECT_THROW(DerivedV4Check(1, 33), isc::OutOfRange);
}
TEST(IpCheck, V4ConstructorInverse) {
TEST(Ipv4Check, V4ConstructorInverse) {
// Valid values. Address/mask of "1" is used as a placeholder
DerivedV4Check acl1(1, 1);
EXPECT_FALSE(acl1.getInverse());
......@@ -118,7 +119,7 @@ TEST(IpCheck, V4ConstructorInverse) {
EXPECT_FALSE(acl3.getInverse());
}
TEST(IpCheck, V4StringConstructor) {
TEST(Ipv4Check, V4StringConstructor) {
DerivedV4Check acl1("127.0.0.1");
uint32_t expected = htonl(0x7f000001);
EXPECT_EQ(expected, acl1.getAddress());
......@@ -143,7 +144,7 @@ TEST(IpCheck, V4StringConstructor) {
// byte order. Therefore for the comparisons to work as expected, we must
// convert the values to network-byte order first.
TEST(IpCheck, V4Compare) {
TEST(Ipv4Check, V4Compare) {
// Exact address - match if given address matches stored address.
DerivedV4Check acl1(htonl(0x23457f13), 32);
EXPECT_TRUE(acl1.matches(htonl(0x23457f13)));
......@@ -173,3 +174,214 @@ TEST(IpCheck, V4Compare) {
EXPECT_TRUE(acl4.matches(htonl(0x2346ffff)));
}
// IPV6 tests
// Declare a derived class to allow the abstract function to be declared
// as a concrete one.
class DerivedV6Check : public Ipv6Check<vector<uint8_t> > {
public:
// Basic constructor
DerivedV6Check(const uint8_t* address, size_t masksize = 128,
bool inverse = false) :
Ipv6Check<vector<uint8_t> >(address, masksize, inverse)
{}
// String constructor
DerivedV6Check(const string& address, bool inverse = false) :
Ipv6Check<vector<uint8_t> >(address, inverse)
{}
// Destructor
virtual ~DerivedV6Check()
{}
// Concrete implementation of abstract method
virtual bool matches(const vector<uint8_t>& context) const {
return (compare(&context[0])); // (compare(context));
}
};
// Some constants used in the tests
static const char* V6ADDR_1_STRING = "2001:0db8:1122:3344:5566:7788:99aa:bbcc";
static const uint8_t V6ADDR_1[] = {
0x20, 0x01, 0x0d, 0xb8, 0x11, 0x22, 0x33, 0x44,
0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc
};
static const char* V6ADDR_2_STRING = "2001:0db8::dead:beef";
static const uint8_t V6ADDR_2[] = {
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
};
// Identical to V6ADDR_2 to 48 bits
static const uint8_t V6ADDR_2_48[] = {
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x55, 0x66,
0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
};
// Identical to V6ADDR_2 to 52 bits
static const uint8_t V6ADDR_2_52[] = {
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x05, 0x66,
0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
};
static const char* V6ADDR_3_STRING = "::1";
static const uint8_t V6ADDR_3[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
// Mask with MS bit set
static const uint8_t MASK_1[] = {
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t MASK_8[] = {
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t MASK_48[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t MASK_51[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t MASK_128[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
// 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.
TEST(Ipv6Check, V6ConstructorAddress) {
DerivedV6Check acl1(V6ADDR_1);
vector<uint8_t> stored = acl1.getAddress();
EXPECT_EQ(sizeof(V6ADDR_1), stored.size());
EXPECT_TRUE(equal(stored.begin(), stored.end(), V6ADDR_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.
TEST(Ipv6Check, V6ConstructorMask) {
// Valid values.
DerivedV6Check acl1(V6ADDR_1, 1);
vector<uint8_t> stored = acl1.getNetmask();
EXPECT_EQ(sizeof(MASK_1), stored.size());
EXPECT_TRUE(equal(stored.begin(), stored.end(), MASK_1));
DerivedV6Check acl2(V6ADDR_1, 8);
stored = acl2.getNetmask();
EXPECT_TRUE(equal(stored.begin(), stored.end(), MASK_8));
DerivedV6Check acl3(V6ADDR_1, 48);
stored = acl3.getNetmask();
EXPECT_TRUE(equal(stored.begin(), stored.end(), MASK_48));
DerivedV6Check acl4(V6ADDR_1, 51);
stored = acl4.getNetmask();
EXPECT_TRUE(equal(stored.begin(), stored.end(), MASK_51));
DerivedV6Check acl5(V6ADDR_1, 128);
stored = acl5.getNetmask();
EXPECT_TRUE(equal(stored.begin(), stored.end(), MASK_128));
// ... and some invalid network masks
EXPECT_THROW(DerivedV6Check(V6ADDR_1, 0), isc::OutOfRange);
EXPECT_THROW(DerivedV6Check(V6ADDR_1, 129), isc::OutOfRange);
}
TEST(Ipv6Check, V6ConstructorInverse) {
// Valid values. Address/mask of "1" is used as a placeholder
DerivedV6Check acl1(V6ADDR_1, 1);
EXPECT_FALSE(acl1.getInverse());
DerivedV6Check acl2(V6ADDR_1, 1, true);
EXPECT_TRUE(acl2.getInverse());
DerivedV6Check acl3(V6ADDR_1, 1, false);
EXPECT_FALSE(acl3.getInverse());
}
TEST(Ipv6Check, V6StringConstructor) {
DerivedV6Check acl1(V6ADDR_1_STRING);
vector<uint8_t> address = acl1.getAddress();
EXPECT_EQ(128, acl1.getMasksize());
EXPECT_TRUE(equal(address.begin(), address.end(), V6ADDR_1));
DerivedV6Check acl2(string(V6ADDR_2_STRING) + string("/48"));
address = acl2.getAddress();
EXPECT_EQ(48, acl2.getMasksize());
EXPECT_TRUE(equal(address.begin(), address.end(), V6ADDR_2));
DerivedV6Check acl3("::1");
address = acl3.getAddress();
EXPECT_EQ(128, acl3.getMasksize());
EXPECT_TRUE(equal(address.begin(), address.end(), V6ADDR_3));
EXPECT_THROW(DerivedV6Check("::1/0"), isc::OutOfRange);
EXPECT_THROW(DerivedV6Check("::1/129"), isc::OutOfRange);
EXPECT_THROW(DerivedV6Check("::1/24/3"), isc::InvalidParameter);
EXPECT_THROW(DerivedV6Check("2001:0db8::dead:beef/ww"), isc::InvalidParameter);
EXPECT_THROW(DerivedV6Check("2xx1:0db8::dead:beef/32"), isc::InvalidParameter);
}
// 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.
TEST(Ipv6Check, V6Compare) {
// Set up some data. The constant doesn't depend on the template parameter,
// so use a type name that's short.
vector<uint8_t> v6addr_2(V6ADDR_2, V6ADDR_2 + DerivedV6Check::IPV6_SIZE);
vector<uint8_t> v6addr_2_48(V6ADDR_2_48, V6ADDR_2_48 + DerivedV6Check::IPV6_SIZE);
vector<uint8_t> v6addr_2_52(V6ADDR_2_52, V6ADDR_2_52 + DerivedV6Check::IPV6_SIZE);
vector<uint8_t> v6addr_3(V6ADDR_3, V6ADDR_3 + DerivedV6Check::IPV6_SIZE);
// Exact address - match if given address matches stored address.
DerivedV6Check acl1(string(V6ADDR_2_STRING) + string("/128"));
EXPECT_TRUE(acl1.matches(v6addr_2));
EXPECT_FALSE(acl1.matches(v6addr_2_52));
EXPECT_FALSE(acl1.matches(v6addr_2_48));
EXPECT_FALSE(acl1.matches(v6addr_3));
// Exact address - match if address does not match stored address
DerivedV6Check acl2(string(V6ADDR_2_STRING) + string("/128"), true);
EXPECT_FALSE(acl2.matches(v6addr_2));
EXPECT_TRUE(acl2.matches(v6addr_2_52));
EXPECT_TRUE(acl2.matches(v6addr_2_48));
EXPECT_TRUE(acl2.matches(v6addr_3));
// Match if the address matches a mask
DerivedV6Check acl3(string(V6ADDR_2_STRING) + string("/52"));
EXPECT_TRUE(acl3.matches(v6addr_2));
EXPECT_TRUE(acl3.matches(v6addr_2_52));
EXPECT_FALSE(acl3.matches(v6addr_2_48));
EXPECT_FALSE(acl3.matches(v6addr_3));
// Match if the address does not match a mask
DerivedV6Check acl4(string(V6ADDR_2_STRING) + string("/52"), true);
EXPECT_FALSE(acl4.matches(v6addr_2));
EXPECT_FALSE(acl4.matches(v6addr_2_52));
EXPECT_TRUE(acl4.matches(v6addr_2_48));
EXPECT_TRUE(acl4.matches(v6addr_3));
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment