Browse Source

[trac998] End of week checkpoint

Stephen Morris 14 years ago
parent
commit
73ac6b09ee

+ 2 - 0
src/lib/acl/Makefile.am

@@ -1,4 +1,6 @@
 SUBDIRS = tests
 SUBDIRS = tests
 
 
+EXTRA_DIST = check.h ip_check.h
+
 # TODO: Once we have some cc file we are able to compile, create the library.
 # TODO: Once we have some cc file we are able to compile, create the library.
 # For now, we have only header files, not creating empty library.
 # For now, we have only header files, not creating empty library.

+ 274 - 0
src/lib/acl/ip_check.h

@@ -0,0 +1,274 @@
+// 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 __IP_CHECK_H
+#define __IP_CHECK_H
+
+#include <boost/lexical_cast.hpp>
+#include <vector>
+
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <acl/check.h>
+#include <util/strutil.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace acl {
+
+/// \brief Convert Mask Size to Mask
+///
+/// Given a mask size and a data type, return a value of that data type with the
+/// most significant maasksize bits set.  For example, if the data type is an
+/// unsigned either-bit byte and the masksize is 3, the function would return
+/// an eight-bit byte with the binary value 11100000.
+///
+/// This is a templated function.  The template parameter must be a signed type.
+///
+/// \param masksize Size of the mask.  This must be between 1 and sizeof(T).
+///        An out of range exception is thrown if this is not the case.
+///
+/// \return Value with the most significant "masksize" bits set.
+
+template <typename T>
+T createNetmask(size_t masksize) {
+
+    if ((masksize > 0) && (masksize <= 8 * sizeof(T))) {
+
+        // To explain the logic, consider a single 4-bit word.  The masksize in
+        // this case can be between 1 and 4.  The following table has the
+        // following columns:
+        //
+        // Mask size (m): number of contiguous bits set
+        //
+        // Low value (lo): unsigned value of 4-bit word with the least-
+        // significant m contiguous bits set.
+        //
+        // High value (hi): unsigned value of 4-bit word with the most-
+        // significant m contiguous bits set.
+        //
+        //    m   lo   hi
+        //    1    1    8
+        //    2    3   12
+        //    3    7   14
+        //    4   15   15
+        //
+        // Clearly the low value is equal to (2**m - 1) (using ** to indicate
+        // exponentiation).  It takes a little thought to see that the high
+        // value is equal to 2**4 - 2**(4-m).  Unfortunately, this formula will
+        // overflow as the intermediate value 2 << sizeof(T) will overflow in an
+        // element of type T.
+        //
+        // However, another way of looking it is that to set the most signifcant
+        // m bits, we set all bits and clear the least-significant 4-m bits.  If
+        // T is a signed value, we can set all bits by setting it to -1.  If m
+        // is 4, we omit clearing any bits, otherwise we clear the bits
+        // represented by the bit pattern 2**(3-m) - 1.  (The value 2**(3-m)
+        // will be greater than 0 and within the range of an unsigned data
+        // type of the same size as T.  So it should not overflow.)
+        //
+        // Therefore we proceed on the assumption that T is signed
+        T mask = -1;
+        if (masksize < 8 * sizeof(T)) {
+            mask &= ~((2 << (8 * sizeof(T) - 1 - masksize)) - 1);
+        }
+
+        return (mask);
+    }
+
+    // Invalid mask size
+    isc_throw(isc::OutOfRange, "mask size of " << masksize << " is invalid " <<
+                               "for the data type which is " << sizeof(T) <<
+                               " bytes long");
+}
+
+/// \brief IP V4 Check
+///
+/// This class performs a match between an IPv4 address specified in an ACL
+/// (IP address, network mask and a flag indicating whether the check should
+/// be for a match or for no-match) and a given IPv4 address.
+///
+/// \param Context Structure holding address to be matched.
+
+template <typename Context> class Ipv4Check : public Check<Context> {
+public:
+
+    /// \brief Constructor
+    ///
+    /// \param address IP address to check for (as an address in host-byte
+    ///        order).
+    /// \param mask The network mask specified as an integer between 1 and
+    ///        32 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.
+    Ipv4Check(uint32_t address, size_t masksize = 32, bool inverse = false) :
+        address_(address), masksize_(masksize), netmask_(0), inverse_(inverse)
+    {
+        init();
+    }
+
+    /// \brief Constructor
+    ///
+    /// \param address IP address and netmask in the form "a.b.c.d/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.
+    Ipv4Check(const std::string& address, bool inverse = false) :
+        address_(0), masksize_(32), netmask_(0), inverse_(inverse)
+    {
+        // See if there is a netmask.
+        std::vector<std::string> components =
+            isc::util::str::tokens(address, "/");
+        if (components.size() == 2) {
+
+            // Yes there is, convert to a mask
+            try {
+                masksize_ = boost::lexical_cast<size_t>(components[1]);
+            } catch (boost::bad_lexical_cast&) {
+                isc_throw(isc::InvalidParameter,
+                          "mask specified in address/mask " << address <<
+                          " is not valid");
+            }
+        } else if (components.size() > 2) {
+            isc_throw(isc::InvalidParameter, "address/mask of " <<
+                      address << " is not valid");
+        }
+
+        // Try to convert the address.
+        int result = inet_pton(AF_INET, components[0].c_str(), &address_);
+        if (result == 0) {
+            isc_throw(isc::InvalidParameter, "address/mask of " <<
+                      address << " is not valid");
+        }
+        address_ = ntohl(address_);
+
+        // All done, so finish initialization.
+        init();
+    }
+
+    /// \brief Destructor
+    virtual ~Ipv4Check() {}
+
+    /// \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.
+    ///
+    /// \param address Address to match against the check condition in the
+    ///        class.
+    ///
+    /// \return true if the address matches, false if it does not.
+    virtual bool compare(uint32_t address) {
+
+        // To check that the address given matches the stored network address
+        // and netmask, we check the simple condition that:
+        //
+        //     address_given & netmask_ == maskaddr_.
+        //
+        // However, we must return the negation of the result if inverse_ is
+        // set.  This leads to the truth table:
+        //
+        // Result inverse_ Return
+        // false  false    false
+        // false  true     true
+        // true   false    true
+        // true   true     false
+        //
+        // ... which is an XOR function.
+
+        return (((address & netmask_) == maskaddr_) ^ inverse_);
+    }
+
+    /// \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 false; }
+
+    /// \brief Estimated cost
+    ///
+    /// Assume that the cost of the match is linear and depends on the number
+    /// of compariosn operations.
+    virtual unsigned cost() const {
+        return (1);             // Single check on a 32-bit word
+    }
+
+    ///@{
+    /// Access methods - mainly for testing
+
+    /// \return Stored IP address
+    uint32_t getAddress() const {
+        return (address_);
+    }
+
+    /// \return Network mask applied to match
+    const uint32_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_);
+    }
+
+    ///@}
+
+private:
+    /// \brief Initialization
+    ///
+    /// Common code shared by all constructors to set up the net mask and
+    /// addresses.
+    void init() {
+        // Validate that the mask is valid.
+        if ((masksize_ >= 1) && (masksize_ <= 32)) {
+
+            // Calculate the bitmask given by the number of bits.
+            netmask_ = isc::acl::createNetmask<int32_t>(masksize_);
+
+            // For speed, store the masked off address.   This saves a mask
+            // operation every time the value is checked.
+            maskaddr_ = address_ & netmask_;
+        } else {
+            isc_throw(isc::OutOfRange,
+                      "mask size of " << masksize_ << " is invalid " <<
+                      "for the data type which is " << sizeof(uint32_t) <<
+                      " bytes long");
+        }
+    }
+
+    uint32_t    address_;   ///< IPv4 address
+    uint32_t    maskaddr_;  ///< Masked IPV4 address
+    size_t      masksize_;  ///< Mask size passed to constructor
+    int32_t     netmask_;   ///< Network mask applied to match
+    bool        inverse_;   ///< test for equality or inequality
+};
+
+} // namespace acl
+} // namespace isc
+
+#endif // __IP_CHECK_H

+ 4 - 0
src/lib/acl/tests/Makefile.am

@@ -5,11 +5,15 @@ if HAVE_GTEST
 TESTS += run_unittests
 TESTS += run_unittests
 run_unittests_SOURCES = run_unittests.cc
 run_unittests_SOURCES = run_unittests.cc
 run_unittests_SOURCES += check_test.cc
 run_unittests_SOURCES += check_test.cc
+run_unittests_SOURCES += ip_check_unittest.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 
 
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 endif
 
 
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)

+ 141 - 0
src/lib/acl/tests/ip_check_unittest.cc

@@ -0,0 +1,141 @@
+// 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 <gtest/gtest.h>
+#include <acl/ip_check.h>
+
+using namespace isc::acl;
+
+/// General tests
+
+TEST(IpCheck, CreateNetmask) {
+    size_t  i;
+
+    // 8-bit tests.
+
+    // Invalid arguments should throw.
+    EXPECT_THROW(createNetmask<int8_t>(0), isc::OutOfRange);
+    EXPECT_THROW(createNetmask<int8_t>(9), isc::OutOfRange);
+
+    // Check on all possible 8-bit values
+    int8_t  expected8;
+    for (i = 1, expected8 = 0x80; i <= 8; ++i, expected8 >>= 1) {
+        EXPECT_EQ(static_cast<int32_t>(expected8),
+                  static_cast<int32_t>(createNetmask<int8_t>(i)));
+    }
+
+    // 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
+    int32_t expected32;
+    for (i = 1, expected32 = 0x80000000; i <= 32; ++i, expected32 >>= 1) {
+        EXPECT_EQ(expected32, createNetmask<int32_t>(i));
+    }
+}
+
+// V4 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.
+
+TEST(IpCheck, V4ConstructorAddress) {
+    // Alternating bits
+    Ipv4Check<int> acl1(0x55555555);
+    EXPECT_EQ(0x55555555, acl1.getAddress());
+
+    Ipv4Check<int> acl2(0xcccccccc);
+    EXPECT_EQ(0xcccccccc, acl2.getAddress());
+}
+
+TEST(IpCheck, V4ConstructorMask) {
+    // Valid values. Address of "1" is used as a placeholder
+    Ipv4Check<int> acl1(1, 1);
+    EXPECT_EQ(0x80000000, acl1.getNetmask());
+    EXPECT_EQ(1, acl1.getMasksize());
+
+    Ipv4Check<int> acl2(1, 24);
+    EXPECT_EQ(0xffffff00, acl2.getNetmask());
+    EXPECT_EQ(24, acl2.getMasksize());
+
+    // ... and some invalid network masks
+    EXPECT_THROW(Ipv4Check<int>(1, 0), isc::OutOfRange);
+}
+
+TEST(IpCheck, V4ConstructorInverse) {
+    // Valid values. Address/mask of "1" is used as a placeholder
+    Ipv4Check<int> acl1(1, 1);
+    EXPECT_FALSE(acl1.getInverse());
+
+    Ipv4Check<int> acl2(1, 1, true);
+    EXPECT_TRUE(acl2.getInverse());
+
+    Ipv4Check<int> acl3(1, 1, false);
+    EXPECT_FALSE(acl3.getInverse());
+}
+
+TEST(IpCheck, V4StringConstructor) {
+    Ipv4Check<int> acl1("127.0.0.1");
+    EXPECT_EQ(0x7f000001, acl1.getAddress());
+    EXPECT_EQ(32, acl1.getMasksize());
+
+    Ipv4Check<int> acl2("255.255.255.0/24");
+    EXPECT_EQ(0xffffff00, acl2.getAddress());
+    EXPECT_EQ(24, acl2.getMasksize());
+
+    EXPECT_THROW(Ipv4Check<int>("255.255.255.0/0"), isc::OutOfRange);
+    EXPECT_THROW(Ipv4Check<int>("255.255.255.0/33"), isc::OutOfRange);
+    EXPECT_THROW(Ipv4Check<int>("255.255.255.0/24/3"), isc::InvalidParameter);
+    EXPECT_THROW(Ipv4Check<int>("255.255.255.0/ww"), isc::InvalidParameter);
+    EXPECT_THROW(Ipv4Check<int>("aa.255.255.0/ww"), isc::InvalidParameter);
+}
+
+// Check that the comparison works - until we have a a message structure,
+// we can't check the matches function.
+
+TEST(IpCheck, V4Compare) {
+    // Exact address - match if given address matches stored address
+    Ipv4Check<int> acl1(0x23457f13, 32);
+    EXPECT_TRUE(acl1.compare(0x23457f13));
+    EXPECT_FALSE(acl1.compare(0x23457f12));
+    EXPECT_FALSE(acl1.compare(0x13457f13));
+
+    // Exact address - match if address does not match stored address
+    Ipv4Check<int> acl2(0x23457f13, 32, true);
+    EXPECT_FALSE(acl2.compare(0x23457f13));
+    EXPECT_TRUE(acl2.compare(0x23457f12));
+    EXPECT_TRUE(acl2.compare(0x13457f13));
+
+    // Match if the address matches a mask
+    Ipv4Check<int> acl3(0x23450000, 16);
+    EXPECT_TRUE(acl3.compare(0x23450000));
+    EXPECT_TRUE(acl3.compare(0x23450001));
+    EXPECT_TRUE(acl3.compare(0x2345ffff));
+    EXPECT_FALSE(acl3.compare(0x23460000));
+    EXPECT_FALSE(acl3.compare(0x2346ffff));
+
+    // Match if the address does not match a mask
+    Ipv4Check<int> acl4(0x23450000, 16, true);
+    EXPECT_FALSE(acl4.compare(0x23450000));
+    EXPECT_FALSE(acl4.compare(0x23450001));
+    EXPECT_FALSE(acl4.compare(0x2345ffff));
+    EXPECT_TRUE(acl4.compare(0x23460000));
+    EXPECT_TRUE(acl4.compare(0x2346ffff));
+
+    // 
+}
+

+ 2 - 1
src/lib/acl/tests/run_unittests.cc

@@ -13,11 +13,12 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
+#include <log/logger_support.h>
 #include <util/unittests/run_all.h>
 #include <util/unittests/run_all.h>
 
 
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
     return (isc::util::unittests::run_all());
     return (isc::util::unittests::run_all());
 }
 }
-