Browse Source

[trac998] IPv6 checks working

Stephen Morris 14 years ago
parent
commit
0c3b69c6e1
2 changed files with 448 additions and 18 deletions
  1. 227 9
      src/lib/acl/ip_check.h
  2. 221 9
      src/lib/acl/tests/ip_check_unittest.cc

+ 227 - 9
src/lib/acl/ip_check.h

@@ -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
 

+ 221 - 9
src/lib/acl/tests/ip_check_unittest.cc

@@ -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));
+}